Change indentation to 4 spaces
This commit is contained in:
parent
e118f3882d
commit
cfca7157b9
165 changed files with 23146 additions and 23975 deletions
|
|
@ -1,14 +1,14 @@
|
|||
---
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
AccessModifierOffset: -8
|
||||
Standard: c++17
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: true
|
||||
AllowShortFunctionsOnASingleLine: true
|
||||
BasedOnStyle: Mozilla
|
||||
ColumnLimit: 100
|
||||
IndentCaseLabels: false
|
||||
IndentWidth: 8
|
||||
IndentWidth: 4
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
PointerAlignment: Right
|
||||
Cpp11BracedListStyle: true
|
||||
|
|
|
|||
|
|
@ -22,45 +22,44 @@ namespace AvatarProvider {
|
|||
void
|
||||
resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback callback)
|
||||
{
|
||||
const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
|
||||
const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
|
||||
|
||||
QPixmap pixmap;
|
||||
if (avatarUrl.isEmpty()) {
|
||||
callback(pixmap);
|
||||
return;
|
||||
}
|
||||
QPixmap pixmap;
|
||||
if (avatarUrl.isEmpty()) {
|
||||
callback(pixmap);
|
||||
return;
|
||||
}
|
||||
|
||||
if (avatar_cache.find(cacheKey, &pixmap)) {
|
||||
callback(pixmap);
|
||||
return;
|
||||
}
|
||||
if (avatar_cache.find(cacheKey, &pixmap)) {
|
||||
callback(pixmap);
|
||||
return;
|
||||
}
|
||||
|
||||
MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")),
|
||||
QSize(size, size),
|
||||
[callback, cacheKey, recv = QPointer<QObject>(receiver)](
|
||||
QString, QSize, QImage img, QString) {
|
||||
if (!recv)
|
||||
return;
|
||||
MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")),
|
||||
QSize(size, size),
|
||||
[callback, cacheKey, recv = QPointer<QObject>(receiver)](
|
||||
QString, QSize, QImage img, QString) {
|
||||
if (!recv)
|
||||
return;
|
||||
|
||||
auto proxy = std::make_shared<AvatarProxy>();
|
||||
QObject::connect(proxy.get(),
|
||||
&AvatarProxy::avatarDownloaded,
|
||||
recv,
|
||||
[callback, cacheKey](QPixmap pm) {
|
||||
if (!pm.isNull())
|
||||
avatar_cache.insert(
|
||||
cacheKey, pm);
|
||||
callback(pm);
|
||||
});
|
||||
auto proxy = std::make_shared<AvatarProxy>();
|
||||
QObject::connect(proxy.get(),
|
||||
&AvatarProxy::avatarDownloaded,
|
||||
recv,
|
||||
[callback, cacheKey](QPixmap pm) {
|
||||
if (!pm.isNull())
|
||||
avatar_cache.insert(cacheKey, pm);
|
||||
callback(pm);
|
||||
});
|
||||
|
||||
if (img.isNull()) {
|
||||
emit proxy->avatarDownloaded(QPixmap{});
|
||||
return;
|
||||
}
|
||||
if (img.isNull()) {
|
||||
emit proxy->avatarDownloaded(QPixmap{});
|
||||
return;
|
||||
}
|
||||
|
||||
auto pm = QPixmap::fromImage(std::move(img));
|
||||
emit proxy->avatarDownloaded(pm);
|
||||
});
|
||||
auto pm = QPixmap::fromImage(std::move(img));
|
||||
emit proxy->avatarDownloaded(pm);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -70,8 +69,8 @@ resolve(const QString &room_id,
|
|||
QObject *receiver,
|
||||
AvatarCallback callback)
|
||||
{
|
||||
auto avatarUrl = cache::avatarUrl(room_id, user_id);
|
||||
auto avatarUrl = cache::avatarUrl(room_id, user_id);
|
||||
|
||||
resolve(std::move(avatarUrl), size, receiver, callback);
|
||||
resolve(std::move(avatarUrl), size, receiver, callback);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ using AvatarCallback = std::function<void(QPixmap)>;
|
|||
|
||||
class AvatarProxy : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void avatarDownloaded(QPixmap pm);
|
||||
void avatarDownloaded(QPixmap pm);
|
||||
};
|
||||
|
||||
namespace AvatarProvider {
|
||||
|
|
|
|||
|
|
@ -13,33 +13,33 @@
|
|||
void
|
||||
BlurhashResponse::run()
|
||||
{
|
||||
if (m_requestedSize.width() < 0 || m_requestedSize.height() < 0) {
|
||||
m_error = QStringLiteral("Blurhash needs size request");
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
if (m_requestedSize.width() == 0 || m_requestedSize.height() == 0) {
|
||||
m_image = QImage(m_requestedSize, QImage::Format_RGB32);
|
||||
m_image.fill(QColor(0, 0, 0));
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
auto decoded = blurhash::decode(QUrl::fromPercentEncoding(m_id.toUtf8()).toStdString(),
|
||||
m_requestedSize.width(),
|
||||
m_requestedSize.height());
|
||||
if (decoded.image.empty()) {
|
||||
m_error = QStringLiteral("Failed decode!");
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
QImage image(decoded.image.data(),
|
||||
(int)decoded.width,
|
||||
(int)decoded.height,
|
||||
(int)decoded.width * 3,
|
||||
QImage::Format_RGB888);
|
||||
|
||||
m_image = image.copy();
|
||||
if (m_requestedSize.width() < 0 || m_requestedSize.height() < 0) {
|
||||
m_error = QStringLiteral("Blurhash needs size request");
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
if (m_requestedSize.width() == 0 || m_requestedSize.height() == 0) {
|
||||
m_image = QImage(m_requestedSize, QImage::Format_RGB32);
|
||||
m_image.fill(QColor(0, 0, 0));
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
auto decoded = blurhash::decode(QUrl::fromPercentEncoding(m_id.toUtf8()).toStdString(),
|
||||
m_requestedSize.width(),
|
||||
m_requestedSize.height());
|
||||
if (decoded.image.empty()) {
|
||||
m_error = QStringLiteral("Failed decode!");
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
QImage image(decoded.image.data(),
|
||||
(int)decoded.width,
|
||||
(int)decoded.height,
|
||||
(int)decoded.width * 3,
|
||||
QImage::Format_RGB888);
|
||||
|
||||
m_image = image.copy();
|
||||
emit finished();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,41 +15,41 @@ class BlurhashResponse
|
|||
, public QRunnable
|
||||
{
|
||||
public:
|
||||
BlurhashResponse(const QString &id, const QSize &requestedSize)
|
||||
BlurhashResponse(const QString &id, const QSize &requestedSize)
|
||||
|
||||
: m_id(id)
|
||||
, m_requestedSize(requestedSize)
|
||||
{
|
||||
setAutoDelete(false);
|
||||
}
|
||||
: m_id(id)
|
||||
, m_requestedSize(requestedSize)
|
||||
{
|
||||
setAutoDelete(false);
|
||||
}
|
||||
|
||||
QQuickTextureFactory *textureFactory() const override
|
||||
{
|
||||
return QQuickTextureFactory::textureFactoryForImage(m_image);
|
||||
}
|
||||
QString errorString() const override { return m_error; }
|
||||
QQuickTextureFactory *textureFactory() const override
|
||||
{
|
||||
return QQuickTextureFactory::textureFactoryForImage(m_image);
|
||||
}
|
||||
QString errorString() const override { return m_error; }
|
||||
|
||||
void run() override;
|
||||
void run() override;
|
||||
|
||||
QString m_id, m_error;
|
||||
QSize m_requestedSize;
|
||||
QImage m_image;
|
||||
QString m_id, m_error;
|
||||
QSize m_requestedSize;
|
||||
QImage m_image;
|
||||
};
|
||||
|
||||
class BlurhashProvider
|
||||
: public QObject
|
||||
, public QQuickAsyncImageProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
QQuickImageResponse *requestImageResponse(const QString &id,
|
||||
const QSize &requestedSize) override
|
||||
{
|
||||
BlurhashResponse *response = new BlurhashResponse(id, requestedSize);
|
||||
pool.start(response);
|
||||
return response;
|
||||
}
|
||||
QQuickImageResponse *requestImageResponse(const QString &id,
|
||||
const QSize &requestedSize) override
|
||||
{
|
||||
BlurhashResponse *response = new BlurhashResponse(id, requestedSize);
|
||||
pool.start(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private:
|
||||
QThreadPool pool;
|
||||
QThreadPool pool;
|
||||
};
|
||||
|
|
|
|||
6408
src/Cache.cpp
6408
src/Cache.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -19,48 +19,48 @@ Q_NAMESPACE
|
|||
//! How much a participant is trusted.
|
||||
enum Trust
|
||||
{
|
||||
Unverified, //! Device unverified or master key changed.
|
||||
TOFU, //! Device is signed by the sender, but the user is not verified, but they never
|
||||
//! changed the master key.
|
||||
Verified, //! User was verified and has crosssigned this device or device is verified.
|
||||
Unverified, //! Device unverified or master key changed.
|
||||
TOFU, //! Device is signed by the sender, but the user is not verified, but they never
|
||||
//! changed the master key.
|
||||
Verified, //! User was verified and has crosssigned this device or device is verified.
|
||||
};
|
||||
Q_ENUM_NS(Trust)
|
||||
}
|
||||
|
||||
struct DeviceKeysToMsgIndex
|
||||
{
|
||||
// map from device key to message_index
|
||||
// Using the device id is safe because we check for reuse on device list updates
|
||||
// Using the device id makes our logic much easier to read.
|
||||
std::map<std::string, uint64_t> deviceids;
|
||||
// map from device key to message_index
|
||||
// Using the device id is safe because we check for reuse on device list updates
|
||||
// Using the device id makes our logic much easier to read.
|
||||
std::map<std::string, uint64_t> deviceids;
|
||||
};
|
||||
|
||||
struct SharedWithUsers
|
||||
{
|
||||
// userid to keys
|
||||
std::map<std::string, DeviceKeysToMsgIndex> keys;
|
||||
// userid to keys
|
||||
std::map<std::string, DeviceKeysToMsgIndex> keys;
|
||||
};
|
||||
|
||||
// Extra information associated with an outbound megolm session.
|
||||
struct GroupSessionData
|
||||
{
|
||||
uint64_t message_index = 0;
|
||||
uint64_t timestamp = 0;
|
||||
uint64_t message_index = 0;
|
||||
uint64_t timestamp = 0;
|
||||
|
||||
// If we got the session via key sharing or forwarding, we can usually trust it.
|
||||
// If it came from asymmetric key backup, it is not trusted.
|
||||
// TODO(Nico): What about forwards? They might come from key backup?
|
||||
bool trusted = true;
|
||||
// If we got the session via key sharing or forwarding, we can usually trust it.
|
||||
// If it came from asymmetric key backup, it is not trusted.
|
||||
// TODO(Nico): What about forwards? They might come from key backup?
|
||||
bool trusted = true;
|
||||
|
||||
std::string sender_claimed_ed25519_key;
|
||||
std::vector<std::string> forwarding_curve25519_key_chain;
|
||||
std::string sender_claimed_ed25519_key;
|
||||
std::vector<std::string> forwarding_curve25519_key_chain;
|
||||
|
||||
//! map from index to event_id to check for replay attacks
|
||||
std::map<uint32_t, std::string> indices;
|
||||
//! map from index to event_id to check for replay attacks
|
||||
std::map<uint32_t, std::string> indices;
|
||||
|
||||
// who has access to this session.
|
||||
// Rotate, when a user leaves the room and share, when a user gets added.
|
||||
SharedWithUsers currently;
|
||||
// who has access to this session.
|
||||
// Rotate, when a user leaves the room and share, when a user gets added.
|
||||
SharedWithUsers currently;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
@ -70,14 +70,14 @@ from_json(const nlohmann::json &obj, GroupSessionData &msg);
|
|||
|
||||
struct OutboundGroupSessionDataRef
|
||||
{
|
||||
mtx::crypto::OutboundGroupSessionPtr session;
|
||||
GroupSessionData data;
|
||||
mtx::crypto::OutboundGroupSessionPtr session;
|
||||
GroupSessionData data;
|
||||
};
|
||||
|
||||
struct DevicePublicKeys
|
||||
{
|
||||
std::string ed25519;
|
||||
std::string curve25519;
|
||||
std::string ed25519;
|
||||
std::string curve25519;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
@ -88,19 +88,19 @@ from_json(const nlohmann::json &obj, DevicePublicKeys &msg);
|
|||
//! Represents a unique megolm session identifier.
|
||||
struct MegolmSessionIndex
|
||||
{
|
||||
MegolmSessionIndex() = default;
|
||||
MegolmSessionIndex(std::string room_id_, const mtx::events::msg::Encrypted &e)
|
||||
: room_id(std::move(room_id_))
|
||||
, session_id(e.session_id)
|
||||
, sender_key(e.sender_key)
|
||||
{}
|
||||
MegolmSessionIndex() = default;
|
||||
MegolmSessionIndex(std::string room_id_, const mtx::events::msg::Encrypted &e)
|
||||
: room_id(std::move(room_id_))
|
||||
, session_id(e.session_id)
|
||||
, sender_key(e.sender_key)
|
||||
{}
|
||||
|
||||
//! The room in which this session exists.
|
||||
std::string room_id;
|
||||
//! The session_id of the megolm session.
|
||||
std::string session_id;
|
||||
//! The curve25519 public key of the sender.
|
||||
std::string sender_key;
|
||||
//! The room in which this session exists.
|
||||
std::string room_id;
|
||||
//! The session_id of the megolm session.
|
||||
std::string session_id;
|
||||
//! The curve25519 public key of the sender.
|
||||
std::string sender_key;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
@ -110,8 +110,8 @@ from_json(const nlohmann::json &obj, MegolmSessionIndex &msg);
|
|||
|
||||
struct StoredOlmSession
|
||||
{
|
||||
std::uint64_t last_message_ts = 0;
|
||||
std::string pickled_session;
|
||||
std::uint64_t last_message_ts = 0;
|
||||
std::string pickled_session;
|
||||
};
|
||||
void
|
||||
to_json(nlohmann::json &obj, const StoredOlmSession &msg);
|
||||
|
|
@ -121,43 +121,43 @@ from_json(const nlohmann::json &obj, StoredOlmSession &msg);
|
|||
//! Verification status of a single user
|
||||
struct VerificationStatus
|
||||
{
|
||||
//! True, if the users master key is verified
|
||||
crypto::Trust user_verified = crypto::Trust::Unverified;
|
||||
//! List of all devices marked as verified
|
||||
std::set<std::string> verified_devices;
|
||||
//! Map from sender key/curve25519 to trust status
|
||||
std::map<std::string, crypto::Trust> verified_device_keys;
|
||||
//! Count of unverified devices
|
||||
int unverified_device_count = 0;
|
||||
// if the keys are not in cache
|
||||
bool no_keys = false;
|
||||
//! True, if the users master key is verified
|
||||
crypto::Trust user_verified = crypto::Trust::Unverified;
|
||||
//! List of all devices marked as verified
|
||||
std::set<std::string> verified_devices;
|
||||
//! Map from sender key/curve25519 to trust status
|
||||
std::map<std::string, crypto::Trust> verified_device_keys;
|
||||
//! Count of unverified devices
|
||||
int unverified_device_count = 0;
|
||||
// if the keys are not in cache
|
||||
bool no_keys = false;
|
||||
};
|
||||
|
||||
//! In memory cache of verification status
|
||||
struct VerificationStorage
|
||||
{
|
||||
//! mapping of user to verification status
|
||||
std::map<std::string, VerificationStatus> status;
|
||||
std::mutex verification_storage_mtx;
|
||||
//! mapping of user to verification status
|
||||
std::map<std::string, VerificationStatus> status;
|
||||
std::mutex verification_storage_mtx;
|
||||
};
|
||||
|
||||
// this will store the keys of the user with whom a encrypted room is shared with
|
||||
struct UserKeyCache
|
||||
{
|
||||
//! Device id to device keys
|
||||
std::map<std::string, mtx::crypto::DeviceKeys> device_keys;
|
||||
//! cross signing keys
|
||||
mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys;
|
||||
//! Sync token when nheko last fetched the keys
|
||||
std::string updated_at;
|
||||
//! Sync token when the keys last changed. updated != last_changed means they are outdated.
|
||||
std::string last_changed;
|
||||
//! if the master key has ever changed
|
||||
bool master_key_changed = false;
|
||||
//! Device keys that were already used at least once
|
||||
std::set<std::string> seen_device_keys;
|
||||
//! Device ids that were already used at least once
|
||||
std::set<std::string> seen_device_ids;
|
||||
//! Device id to device keys
|
||||
std::map<std::string, mtx::crypto::DeviceKeys> device_keys;
|
||||
//! cross signing keys
|
||||
mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys;
|
||||
//! Sync token when nheko last fetched the keys
|
||||
std::string updated_at;
|
||||
//! Sync token when the keys last changed. updated != last_changed means they are outdated.
|
||||
std::string last_changed;
|
||||
//! if the master key has ever changed
|
||||
bool master_key_changed = false;
|
||||
//! Device keys that were already used at least once
|
||||
std::set<std::string> seen_device_keys;
|
||||
//! Device ids that were already used at least once
|
||||
std::set<std::string> seen_device_ids;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
@ -169,10 +169,10 @@ from_json(const nlohmann::json &j, UserKeyCache &info);
|
|||
// UserKeyCache stores only keys of users with which encrypted room is shared
|
||||
struct VerificationCache
|
||||
{
|
||||
//! list of verified device_ids with device-verification
|
||||
std::set<std::string> device_verified;
|
||||
//! list of devices the user blocks
|
||||
std::set<std::string> device_blocked;
|
||||
//! list of verified device_ids with device-verification
|
||||
std::set<std::string> device_verified;
|
||||
//! list of devices the user blocks
|
||||
std::set<std::string> device_blocked;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
@ -182,10 +182,10 @@ from_json(const nlohmann::json &j, VerificationCache &info);
|
|||
|
||||
struct OnlineBackupVersion
|
||||
{
|
||||
//! the version of the online backup currently enabled
|
||||
std::string version;
|
||||
//! the algorithm used by the backup
|
||||
std::string algorithm;
|
||||
//! the version of the online backup currently enabled
|
||||
std::string version;
|
||||
//! the algorithm used by the backup
|
||||
std::string algorithm;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -16,23 +16,23 @@
|
|||
namespace cache {
|
||||
enum class CacheVersion : int
|
||||
{
|
||||
Older = -1,
|
||||
Current = 0,
|
||||
Newer = 1,
|
||||
Older = -1,
|
||||
Current = 0,
|
||||
Newer = 1,
|
||||
};
|
||||
}
|
||||
|
||||
struct RoomMember
|
||||
{
|
||||
QString user_id;
|
||||
QString display_name;
|
||||
QString user_id;
|
||||
QString display_name;
|
||||
};
|
||||
|
||||
//! Used to uniquely identify a list of read receipts.
|
||||
struct ReadReceiptKey
|
||||
{
|
||||
std::string event_id;
|
||||
std::string room_id;
|
||||
std::string event_id;
|
||||
std::string room_id;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
@ -43,49 +43,49 @@ from_json(const nlohmann::json &j, ReadReceiptKey &key);
|
|||
|
||||
struct DescInfo
|
||||
{
|
||||
QString event_id;
|
||||
QString userid;
|
||||
QString body;
|
||||
QString descriptiveTime;
|
||||
uint64_t timestamp;
|
||||
QDateTime datetime;
|
||||
QString event_id;
|
||||
QString userid;
|
||||
QString body;
|
||||
QString descriptiveTime;
|
||||
uint64_t timestamp;
|
||||
QDateTime datetime;
|
||||
};
|
||||
|
||||
inline bool
|
||||
operator==(const DescInfo &a, const DescInfo &b)
|
||||
{
|
||||
return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) ==
|
||||
std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
|
||||
return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) ==
|
||||
std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
|
||||
}
|
||||
inline bool
|
||||
operator!=(const DescInfo &a, const DescInfo &b)
|
||||
{
|
||||
return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) !=
|
||||
std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
|
||||
return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) !=
|
||||
std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
|
||||
}
|
||||
|
||||
//! UI info associated with a room.
|
||||
struct RoomInfo
|
||||
{
|
||||
//! The calculated name of the room.
|
||||
std::string name;
|
||||
//! The topic of the room.
|
||||
std::string topic;
|
||||
//! The calculated avatar url of the room.
|
||||
std::string avatar_url;
|
||||
//! The calculated version of this room set at creation time.
|
||||
std::string version;
|
||||
//! Whether or not the room is an invite.
|
||||
bool is_invite = false;
|
||||
//! Wheter or not the room is a space
|
||||
bool is_space = false;
|
||||
//! Total number of members in the room.
|
||||
size_t member_count = 0;
|
||||
//! Who can access to the room.
|
||||
mtx::events::state::JoinRule join_rule = mtx::events::state::JoinRule::Public;
|
||||
bool guest_access = false;
|
||||
//! The list of tags associated with this room
|
||||
std::vector<std::string> tags;
|
||||
//! The calculated name of the room.
|
||||
std::string name;
|
||||
//! The topic of the room.
|
||||
std::string topic;
|
||||
//! The calculated avatar url of the room.
|
||||
std::string avatar_url;
|
||||
//! The calculated version of this room set at creation time.
|
||||
std::string version;
|
||||
//! Whether or not the room is an invite.
|
||||
bool is_invite = false;
|
||||
//! Wheter or not the room is a space
|
||||
bool is_space = false;
|
||||
//! Total number of members in the room.
|
||||
size_t member_count = 0;
|
||||
//! Who can access to the room.
|
||||
mtx::events::state::JoinRule join_rule = mtx::events::state::JoinRule::Public;
|
||||
bool guest_access = false;
|
||||
//! The list of tags associated with this room
|
||||
std::vector<std::string> tags;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
@ -96,8 +96,8 @@ from_json(const nlohmann::json &j, RoomInfo &info);
|
|||
//! Basic information per member.
|
||||
struct MemberInfo
|
||||
{
|
||||
std::string name;
|
||||
std::string avatar_url;
|
||||
std::string name;
|
||||
std::string avatar_url;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
@ -107,13 +107,13 @@ from_json(const nlohmann::json &j, MemberInfo &info);
|
|||
|
||||
struct RoomSearchResult
|
||||
{
|
||||
std::string room_id;
|
||||
RoomInfo info;
|
||||
std::string room_id;
|
||||
RoomInfo info;
|
||||
};
|
||||
|
||||
struct ImagePackInfo
|
||||
{
|
||||
mtx::events::msc2545::ImagePack pack;
|
||||
std::string source_room;
|
||||
std::string state_key;
|
||||
mtx::events::msc2545::ImagePack pack;
|
||||
std::string source_room;
|
||||
std::string state_key;
|
||||
};
|
||||
|
|
|
|||
1172
src/Cache_p.h
1172
src/Cache_p.h
File diff suppressed because it is too large
Load diff
|
|
@ -27,20 +27,20 @@ namespace {
|
|||
|
||||
struct AudioSource
|
||||
{
|
||||
std::string name;
|
||||
GstDevice *device;
|
||||
std::string name;
|
||||
GstDevice *device;
|
||||
};
|
||||
|
||||
struct VideoSource
|
||||
{
|
||||
struct Caps
|
||||
{
|
||||
std::string resolution;
|
||||
std::vector<std::string> frameRates;
|
||||
};
|
||||
std::string name;
|
||||
GstDevice *device;
|
||||
std::vector<Caps> caps;
|
||||
struct Caps
|
||||
{
|
||||
std::string resolution;
|
||||
std::vector<std::string> frameRates;
|
||||
};
|
||||
std::string name;
|
||||
GstDevice *device;
|
||||
std::vector<Caps> caps;
|
||||
};
|
||||
|
||||
std::vector<AudioSource> audioSources_;
|
||||
|
|
@ -50,315 +50,304 @@ using FrameRate = std::pair<int, int>;
|
|||
std::optional<FrameRate>
|
||||
getFrameRate(const GValue *value)
|
||||
{
|
||||
if (GST_VALUE_HOLDS_FRACTION(value)) {
|
||||
gint num = gst_value_get_fraction_numerator(value);
|
||||
gint den = gst_value_get_fraction_denominator(value);
|
||||
return FrameRate{num, den};
|
||||
}
|
||||
return std::nullopt;
|
||||
if (GST_VALUE_HOLDS_FRACTION(value)) {
|
||||
gint num = gst_value_get_fraction_numerator(value);
|
||||
gint den = gst_value_get_fraction_denominator(value);
|
||||
return FrameRate{num, den};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void
|
||||
addFrameRate(std::vector<std::string> &rates, const FrameRate &rate)
|
||||
{
|
||||
constexpr double minimumFrameRate = 15.0;
|
||||
if (static_cast<double>(rate.first) / rate.second >= minimumFrameRate)
|
||||
rates.push_back(std::to_string(rate.first) + "/" + std::to_string(rate.second));
|
||||
constexpr double minimumFrameRate = 15.0;
|
||||
if (static_cast<double>(rate.first) / rate.second >= minimumFrameRate)
|
||||
rates.push_back(std::to_string(rate.first) + "/" + std::to_string(rate.second));
|
||||
}
|
||||
|
||||
void
|
||||
setDefaultDevice(bool isVideo)
|
||||
{
|
||||
auto settings = ChatPage::instance()->userSettings();
|
||||
if (isVideo && settings->camera().isEmpty()) {
|
||||
const VideoSource &camera = videoSources_.front();
|
||||
settings->setCamera(QString::fromStdString(camera.name));
|
||||
settings->setCameraResolution(
|
||||
QString::fromStdString(camera.caps.front().resolution));
|
||||
settings->setCameraFrameRate(
|
||||
QString::fromStdString(camera.caps.front().frameRates.front()));
|
||||
} else if (!isVideo && settings->microphone().isEmpty()) {
|
||||
settings->setMicrophone(QString::fromStdString(audioSources_.front().name));
|
||||
}
|
||||
auto settings = ChatPage::instance()->userSettings();
|
||||
if (isVideo && settings->camera().isEmpty()) {
|
||||
const VideoSource &camera = videoSources_.front();
|
||||
settings->setCamera(QString::fromStdString(camera.name));
|
||||
settings->setCameraResolution(QString::fromStdString(camera.caps.front().resolution));
|
||||
settings->setCameraFrameRate(
|
||||
QString::fromStdString(camera.caps.front().frameRates.front()));
|
||||
} else if (!isVideo && settings->microphone().isEmpty()) {
|
||||
settings->setMicrophone(QString::fromStdString(audioSources_.front().name));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
addDevice(GstDevice *device)
|
||||
{
|
||||
if (!device)
|
||||
return;
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
gchar *name = gst_device_get_display_name(device);
|
||||
gchar *type = gst_device_get_device_class(device);
|
||||
bool isVideo = !std::strncmp(type, "Video", 5);
|
||||
g_free(type);
|
||||
nhlog::ui()->debug("WebRTC: {} device added: {}", isVideo ? "video" : "audio", name);
|
||||
if (!isVideo) {
|
||||
audioSources_.push_back({name, device});
|
||||
g_free(name);
|
||||
setDefaultDevice(false);
|
||||
return;
|
||||
}
|
||||
|
||||
GstCaps *gstcaps = gst_device_get_caps(device);
|
||||
if (!gstcaps) {
|
||||
nhlog::ui()->debug("WebRTC: unable to get caps for {}", name);
|
||||
g_free(name);
|
||||
return;
|
||||
}
|
||||
|
||||
VideoSource source{name, device, {}};
|
||||
gchar *name = gst_device_get_display_name(device);
|
||||
gchar *type = gst_device_get_device_class(device);
|
||||
bool isVideo = !std::strncmp(type, "Video", 5);
|
||||
g_free(type);
|
||||
nhlog::ui()->debug("WebRTC: {} device added: {}", isVideo ? "video" : "audio", name);
|
||||
if (!isVideo) {
|
||||
audioSources_.push_back({name, device});
|
||||
g_free(name);
|
||||
guint nCaps = gst_caps_get_size(gstcaps);
|
||||
for (guint i = 0; i < nCaps; ++i) {
|
||||
GstStructure *structure = gst_caps_get_structure(gstcaps, i);
|
||||
const gchar *struct_name = gst_structure_get_name(structure);
|
||||
if (!std::strcmp(struct_name, "video/x-raw")) {
|
||||
gint widthpx, heightpx;
|
||||
if (gst_structure_get(structure,
|
||||
"width",
|
||||
G_TYPE_INT,
|
||||
&widthpx,
|
||||
"height",
|
||||
G_TYPE_INT,
|
||||
&heightpx,
|
||||
nullptr)) {
|
||||
VideoSource::Caps caps;
|
||||
caps.resolution =
|
||||
std::to_string(widthpx) + "x" + std::to_string(heightpx);
|
||||
const GValue *value =
|
||||
gst_structure_get_value(structure, "framerate");
|
||||
if (auto fr = getFrameRate(value); fr)
|
||||
addFrameRate(caps.frameRates, *fr);
|
||||
else if (GST_VALUE_HOLDS_FRACTION_RANGE(value)) {
|
||||
addFrameRate(
|
||||
caps.frameRates,
|
||||
*getFrameRate(gst_value_get_fraction_range_min(value)));
|
||||
addFrameRate(
|
||||
caps.frameRates,
|
||||
*getFrameRate(gst_value_get_fraction_range_max(value)));
|
||||
} else if (GST_VALUE_HOLDS_LIST(value)) {
|
||||
guint nRates = gst_value_list_get_size(value);
|
||||
for (guint j = 0; j < nRates; ++j) {
|
||||
const GValue *rate =
|
||||
gst_value_list_get_value(value, j);
|
||||
if (auto frate = getFrameRate(rate); frate)
|
||||
addFrameRate(caps.frameRates, *frate);
|
||||
}
|
||||
}
|
||||
if (!caps.frameRates.empty())
|
||||
source.caps.push_back(std::move(caps));
|
||||
}
|
||||
setDefaultDevice(false);
|
||||
return;
|
||||
}
|
||||
|
||||
GstCaps *gstcaps = gst_device_get_caps(device);
|
||||
if (!gstcaps) {
|
||||
nhlog::ui()->debug("WebRTC: unable to get caps for {}", name);
|
||||
g_free(name);
|
||||
return;
|
||||
}
|
||||
|
||||
VideoSource source{name, device, {}};
|
||||
g_free(name);
|
||||
guint nCaps = gst_caps_get_size(gstcaps);
|
||||
for (guint i = 0; i < nCaps; ++i) {
|
||||
GstStructure *structure = gst_caps_get_structure(gstcaps, i);
|
||||
const gchar *struct_name = gst_structure_get_name(structure);
|
||||
if (!std::strcmp(struct_name, "video/x-raw")) {
|
||||
gint widthpx, heightpx;
|
||||
if (gst_structure_get(structure,
|
||||
"width",
|
||||
G_TYPE_INT,
|
||||
&widthpx,
|
||||
"height",
|
||||
G_TYPE_INT,
|
||||
&heightpx,
|
||||
nullptr)) {
|
||||
VideoSource::Caps caps;
|
||||
caps.resolution = std::to_string(widthpx) + "x" + std::to_string(heightpx);
|
||||
const GValue *value = gst_structure_get_value(structure, "framerate");
|
||||
if (auto fr = getFrameRate(value); fr)
|
||||
addFrameRate(caps.frameRates, *fr);
|
||||
else if (GST_VALUE_HOLDS_FRACTION_RANGE(value)) {
|
||||
addFrameRate(caps.frameRates,
|
||||
*getFrameRate(gst_value_get_fraction_range_min(value)));
|
||||
addFrameRate(caps.frameRates,
|
||||
*getFrameRate(gst_value_get_fraction_range_max(value)));
|
||||
} else if (GST_VALUE_HOLDS_LIST(value)) {
|
||||
guint nRates = gst_value_list_get_size(value);
|
||||
for (guint j = 0; j < nRates; ++j) {
|
||||
const GValue *rate = gst_value_list_get_value(value, j);
|
||||
if (auto frate = getFrameRate(rate); frate)
|
||||
addFrameRate(caps.frameRates, *frate);
|
||||
}
|
||||
}
|
||||
if (!caps.frameRates.empty())
|
||||
source.caps.push_back(std::move(caps));
|
||||
}
|
||||
}
|
||||
gst_caps_unref(gstcaps);
|
||||
videoSources_.push_back(std::move(source));
|
||||
setDefaultDevice(true);
|
||||
}
|
||||
gst_caps_unref(gstcaps);
|
||||
videoSources_.push_back(std::move(source));
|
||||
setDefaultDevice(true);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool
|
||||
removeDevice(T &sources, GstDevice *device, bool changed)
|
||||
{
|
||||
if (auto it = std::find_if(sources.begin(),
|
||||
sources.end(),
|
||||
[device](const auto &s) { return s.device == device; });
|
||||
it != sources.end()) {
|
||||
nhlog::ui()->debug(std::string("WebRTC: device ") +
|
||||
(changed ? "changed: " : "removed: ") + "{}",
|
||||
it->name);
|
||||
gst_object_unref(device);
|
||||
sources.erase(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (auto it = std::find_if(
|
||||
sources.begin(), sources.end(), [device](const auto &s) { return s.device == device; });
|
||||
it != sources.end()) {
|
||||
nhlog::ui()->debug(
|
||||
std::string("WebRTC: device ") + (changed ? "changed: " : "removed: ") + "{}", it->name);
|
||||
gst_object_unref(device);
|
||||
sources.erase(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
removeDevice(GstDevice *device, bool changed)
|
||||
{
|
||||
if (device) {
|
||||
if (removeDevice(audioSources_, device, changed) ||
|
||||
removeDevice(videoSources_, device, changed))
|
||||
return;
|
||||
}
|
||||
if (device) {
|
||||
if (removeDevice(audioSources_, device, changed) ||
|
||||
removeDevice(videoSources_, device, changed))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data G_GNUC_UNUSED)
|
||||
{
|
||||
switch (GST_MESSAGE_TYPE(msg)) {
|
||||
case GST_MESSAGE_DEVICE_ADDED: {
|
||||
GstDevice *device;
|
||||
gst_message_parse_device_added(msg, &device);
|
||||
addDevice(device);
|
||||
emit CallDevices::instance().devicesChanged();
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_DEVICE_REMOVED: {
|
||||
GstDevice *device;
|
||||
gst_message_parse_device_removed(msg, &device);
|
||||
removeDevice(device, false);
|
||||
emit CallDevices::instance().devicesChanged();
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_DEVICE_CHANGED: {
|
||||
GstDevice *device;
|
||||
GstDevice *oldDevice;
|
||||
gst_message_parse_device_changed(msg, &device, &oldDevice);
|
||||
removeDevice(oldDevice, true);
|
||||
addDevice(device);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
switch (GST_MESSAGE_TYPE(msg)) {
|
||||
case GST_MESSAGE_DEVICE_ADDED: {
|
||||
GstDevice *device;
|
||||
gst_message_parse_device_added(msg, &device);
|
||||
addDevice(device);
|
||||
emit CallDevices::instance().devicesChanged();
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_DEVICE_REMOVED: {
|
||||
GstDevice *device;
|
||||
gst_message_parse_device_removed(msg, &device);
|
||||
removeDevice(device, false);
|
||||
emit CallDevices::instance().devicesChanged();
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_DEVICE_CHANGED: {
|
||||
GstDevice *device;
|
||||
GstDevice *oldDevice;
|
||||
gst_message_parse_device_changed(msg, &device, &oldDevice);
|
||||
removeDevice(oldDevice, true);
|
||||
addDevice(device);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<std::string>
|
||||
deviceNames(T &sources, const std::string &defaultDevice)
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
ret.reserve(sources.size());
|
||||
for (const auto &s : sources)
|
||||
ret.push_back(s.name);
|
||||
std::vector<std::string> ret;
|
||||
ret.reserve(sources.size());
|
||||
for (const auto &s : sources)
|
||||
ret.push_back(s.name);
|
||||
|
||||
// move default device to top of the list
|
||||
if (auto it = std::find(ret.begin(), ret.end(), defaultDevice); it != ret.end())
|
||||
std::swap(ret.front(), *it);
|
||||
// move default device to top of the list
|
||||
if (auto it = std::find(ret.begin(), ret.end(), defaultDevice); it != ret.end())
|
||||
std::swap(ret.front(), *it);
|
||||
|
||||
return ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<VideoSource>
|
||||
getVideoSource(const std::string &cameraName)
|
||||
{
|
||||
if (auto it = std::find_if(videoSources_.cbegin(),
|
||||
videoSources_.cend(),
|
||||
[&cameraName](const auto &s) { return s.name == cameraName; });
|
||||
it != videoSources_.cend()) {
|
||||
return *it;
|
||||
}
|
||||
return std::nullopt;
|
||||
if (auto it = std::find_if(videoSources_.cbegin(),
|
||||
videoSources_.cend(),
|
||||
[&cameraName](const auto &s) { return s.name == cameraName; });
|
||||
it != videoSources_.cend()) {
|
||||
return *it;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::pair<int, int>
|
||||
tokenise(std::string_view str, char delim)
|
||||
{
|
||||
std::pair<int, int> ret;
|
||||
ret.first = std::atoi(str.data());
|
||||
auto pos = str.find_first_of(delim);
|
||||
ret.second = std::atoi(str.data() + pos + 1);
|
||||
return ret;
|
||||
std::pair<int, int> ret;
|
||||
ret.first = std::atoi(str.data());
|
||||
auto pos = str.find_first_of(delim);
|
||||
ret.second = std::atoi(str.data() + pos + 1);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CallDevices::init()
|
||||
{
|
||||
static GstDeviceMonitor *monitor = nullptr;
|
||||
if (!monitor) {
|
||||
monitor = gst_device_monitor_new();
|
||||
GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
|
||||
gst_device_monitor_add_filter(monitor, "Audio/Source", caps);
|
||||
gst_device_monitor_add_filter(monitor, "Audio/Duplex", caps);
|
||||
gst_caps_unref(caps);
|
||||
caps = gst_caps_new_empty_simple("video/x-raw");
|
||||
gst_device_monitor_add_filter(monitor, "Video/Source", caps);
|
||||
gst_device_monitor_add_filter(monitor, "Video/Duplex", caps);
|
||||
gst_caps_unref(caps);
|
||||
static GstDeviceMonitor *monitor = nullptr;
|
||||
if (!monitor) {
|
||||
monitor = gst_device_monitor_new();
|
||||
GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
|
||||
gst_device_monitor_add_filter(monitor, "Audio/Source", caps);
|
||||
gst_device_monitor_add_filter(monitor, "Audio/Duplex", caps);
|
||||
gst_caps_unref(caps);
|
||||
caps = gst_caps_new_empty_simple("video/x-raw");
|
||||
gst_device_monitor_add_filter(monitor, "Video/Source", caps);
|
||||
gst_device_monitor_add_filter(monitor, "Video/Duplex", caps);
|
||||
gst_caps_unref(caps);
|
||||
|
||||
GstBus *bus = gst_device_monitor_get_bus(monitor);
|
||||
gst_bus_add_watch(bus, newBusMessage, nullptr);
|
||||
gst_object_unref(bus);
|
||||
if (!gst_device_monitor_start(monitor)) {
|
||||
nhlog::ui()->error("WebRTC: failed to start device monitor");
|
||||
return;
|
||||
}
|
||||
GstBus *bus = gst_device_monitor_get_bus(monitor);
|
||||
gst_bus_add_watch(bus, newBusMessage, nullptr);
|
||||
gst_object_unref(bus);
|
||||
if (!gst_device_monitor_start(monitor)) {
|
||||
nhlog::ui()->error("WebRTC: failed to start device monitor");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
CallDevices::haveMic() const
|
||||
{
|
||||
return !audioSources_.empty();
|
||||
return !audioSources_.empty();
|
||||
}
|
||||
|
||||
bool
|
||||
CallDevices::haveCamera() const
|
||||
{
|
||||
return !videoSources_.empty();
|
||||
return !videoSources_.empty();
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::names(bool isVideo, const std::string &defaultDevice) const
|
||||
{
|
||||
return isVideo ? deviceNames(videoSources_, defaultDevice)
|
||||
: deviceNames(audioSources_, defaultDevice);
|
||||
return isVideo ? deviceNames(videoSources_, defaultDevice)
|
||||
: deviceNames(audioSources_, defaultDevice);
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::resolutions(const std::string &cameraName) const
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
if (auto s = getVideoSource(cameraName); s) {
|
||||
ret.reserve(s->caps.size());
|
||||
for (const auto &c : s->caps)
|
||||
ret.push_back(c.resolution);
|
||||
}
|
||||
return ret;
|
||||
std::vector<std::string> ret;
|
||||
if (auto s = getVideoSource(cameraName); s) {
|
||||
ret.reserve(s->caps.size());
|
||||
for (const auto &c : s->caps)
|
||||
ret.push_back(c.resolution);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::frameRates(const std::string &cameraName, const std::string &resolution) const
|
||||
{
|
||||
if (auto s = getVideoSource(cameraName); s) {
|
||||
if (auto it =
|
||||
std::find_if(s->caps.cbegin(),
|
||||
if (auto s = getVideoSource(cameraName); s) {
|
||||
if (auto it = std::find_if(s->caps.cbegin(),
|
||||
s->caps.cend(),
|
||||
[&](const auto &c) { return c.resolution == resolution; });
|
||||
it != s->caps.cend())
|
||||
return it->frameRates;
|
||||
}
|
||||
return {};
|
||||
it != s->caps.cend())
|
||||
return it->frameRates;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
GstDevice *
|
||||
CallDevices::audioDevice() const
|
||||
{
|
||||
std::string name = ChatPage::instance()->userSettings()->microphone().toStdString();
|
||||
if (auto it = std::find_if(audioSources_.cbegin(),
|
||||
audioSources_.cend(),
|
||||
[&name](const auto &s) { return s.name == name; });
|
||||
it != audioSources_.cend()) {
|
||||
nhlog::ui()->debug("WebRTC: microphone: {}", name);
|
||||
return it->device;
|
||||
} else {
|
||||
nhlog::ui()->error("WebRTC: unknown microphone: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
std::string name = ChatPage::instance()->userSettings()->microphone().toStdString();
|
||||
if (auto it = std::find_if(audioSources_.cbegin(),
|
||||
audioSources_.cend(),
|
||||
[&name](const auto &s) { return s.name == name; });
|
||||
it != audioSources_.cend()) {
|
||||
nhlog::ui()->debug("WebRTC: microphone: {}", name);
|
||||
return it->device;
|
||||
} else {
|
||||
nhlog::ui()->error("WebRTC: unknown microphone: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
GstDevice *
|
||||
CallDevices::videoDevice(std::pair<int, int> &resolution, std::pair<int, int> &frameRate) const
|
||||
{
|
||||
auto settings = ChatPage::instance()->userSettings();
|
||||
std::string name = settings->camera().toStdString();
|
||||
if (auto s = getVideoSource(name); s) {
|
||||
nhlog::ui()->debug("WebRTC: camera: {}", name);
|
||||
resolution = tokenise(settings->cameraResolution().toStdString(), 'x');
|
||||
frameRate = tokenise(settings->cameraFrameRate().toStdString(), '/');
|
||||
nhlog::ui()->debug(
|
||||
"WebRTC: camera resolution: {}x{}", resolution.first, resolution.second);
|
||||
nhlog::ui()->debug(
|
||||
"WebRTC: camera frame rate: {}/{}", frameRate.first, frameRate.second);
|
||||
return s->device;
|
||||
} else {
|
||||
nhlog::ui()->error("WebRTC: unknown camera: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
auto settings = ChatPage::instance()->userSettings();
|
||||
std::string name = settings->camera().toStdString();
|
||||
if (auto s = getVideoSource(name); s) {
|
||||
nhlog::ui()->debug("WebRTC: camera: {}", name);
|
||||
resolution = tokenise(settings->cameraResolution().toStdString(), 'x');
|
||||
frameRate = tokenise(settings->cameraFrameRate().toStdString(), '/');
|
||||
nhlog::ui()->debug("WebRTC: camera resolution: {}x{}", resolution.first, resolution.second);
|
||||
nhlog::ui()->debug("WebRTC: camera frame rate: {}/{}", frameRate.first, frameRate.second);
|
||||
return s->device;
|
||||
} else {
|
||||
nhlog::ui()->error("WebRTC: unknown camera: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
@ -366,31 +355,31 @@ CallDevices::videoDevice(std::pair<int, int> &resolution, std::pair<int, int> &f
|
|||
bool
|
||||
CallDevices::haveMic() const
|
||||
{
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
CallDevices::haveCamera() const
|
||||
{
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::names(bool, const std::string &) const
|
||||
{
|
||||
return {};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::resolutions(const std::string &) const
|
||||
{
|
||||
return {};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::frameRates(const std::string &, const std::string &) const
|
||||
{
|
||||
return {};
|
||||
return {};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -14,35 +14,34 @@ typedef struct _GstDevice GstDevice;
|
|||
|
||||
class CallDevices : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static CallDevices &instance()
|
||||
{
|
||||
static CallDevices instance;
|
||||
return instance;
|
||||
}
|
||||
static CallDevices &instance()
|
||||
{
|
||||
static CallDevices instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool haveMic() const;
|
||||
bool haveCamera() const;
|
||||
std::vector<std::string> names(bool isVideo, const std::string &defaultDevice) const;
|
||||
std::vector<std::string> resolutions(const std::string &cameraName) const;
|
||||
std::vector<std::string> frameRates(const std::string &cameraName,
|
||||
const std::string &resolution) const;
|
||||
bool haveMic() const;
|
||||
bool haveCamera() const;
|
||||
std::vector<std::string> names(bool isVideo, const std::string &defaultDevice) const;
|
||||
std::vector<std::string> resolutions(const std::string &cameraName) const;
|
||||
std::vector<std::string> frameRates(const std::string &cameraName,
|
||||
const std::string &resolution) const;
|
||||
|
||||
signals:
|
||||
void devicesChanged();
|
||||
void devicesChanged();
|
||||
|
||||
private:
|
||||
CallDevices();
|
||||
CallDevices();
|
||||
|
||||
friend class WebRTCSession;
|
||||
void init();
|
||||
GstDevice *audioDevice() const;
|
||||
GstDevice *videoDevice(std::pair<int, int> &resolution,
|
||||
std::pair<int, int> &frameRate) const;
|
||||
friend class WebRTCSession;
|
||||
void init();
|
||||
GstDevice *audioDevice() const;
|
||||
GstDevice *videoDevice(std::pair<int, int> &resolution, std::pair<int, int> &frameRate) const;
|
||||
|
||||
public:
|
||||
CallDevices(CallDevices const &) = delete;
|
||||
void operator=(CallDevices const &) = delete;
|
||||
CallDevices(CallDevices const &) = delete;
|
||||
void operator=(CallDevices const &) = delete;
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -26,93 +26,92 @@ class QUrl;
|
|||
|
||||
class CallManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState)
|
||||
Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState)
|
||||
Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState)
|
||||
Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState)
|
||||
Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState)
|
||||
Q_PROPERTY(QString callPartyDisplayName READ callPartyDisplayName NOTIFY newInviteState)
|
||||
Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState)
|
||||
Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
|
||||
Q_PROPERTY(bool haveLocalPiP READ haveLocalPiP NOTIFY newCallState)
|
||||
Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged)
|
||||
Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged)
|
||||
Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT)
|
||||
Q_PROPERTY(bool screenShareSupported READ screenShareSupported CONSTANT)
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState)
|
||||
Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState)
|
||||
Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState)
|
||||
Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState)
|
||||
Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState)
|
||||
Q_PROPERTY(QString callPartyDisplayName READ callPartyDisplayName NOTIFY newInviteState)
|
||||
Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState)
|
||||
Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
|
||||
Q_PROPERTY(bool haveLocalPiP READ haveLocalPiP NOTIFY newCallState)
|
||||
Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged)
|
||||
Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged)
|
||||
Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT)
|
||||
Q_PROPERTY(bool screenShareSupported READ screenShareSupported CONSTANT)
|
||||
|
||||
public:
|
||||
CallManager(QObject *);
|
||||
CallManager(QObject *);
|
||||
|
||||
bool haveCallInvite() const { return haveCallInvite_; }
|
||||
bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; }
|
||||
webrtc::CallType callType() const { return callType_; }
|
||||
webrtc::State callState() const { return session_.state(); }
|
||||
QString callParty() const { return callParty_; }
|
||||
QString callPartyDisplayName() const { return callPartyDisplayName_; }
|
||||
QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
|
||||
bool isMicMuted() const { return session_.isMicMuted(); }
|
||||
bool haveLocalPiP() const { return session_.haveLocalPiP(); }
|
||||
QStringList mics() const { return devices(false); }
|
||||
QStringList cameras() const { return devices(true); }
|
||||
void refreshTurnServer();
|
||||
bool haveCallInvite() const { return haveCallInvite_; }
|
||||
bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; }
|
||||
webrtc::CallType callType() const { return callType_; }
|
||||
webrtc::State callState() const { return session_.state(); }
|
||||
QString callParty() const { return callParty_; }
|
||||
QString callPartyDisplayName() const { return callPartyDisplayName_; }
|
||||
QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
|
||||
bool isMicMuted() const { return session_.isMicMuted(); }
|
||||
bool haveLocalPiP() const { return session_.haveLocalPiP(); }
|
||||
QStringList mics() const { return devices(false); }
|
||||
QStringList cameras() const { return devices(true); }
|
||||
void refreshTurnServer();
|
||||
|
||||
static bool callsSupported();
|
||||
static bool screenShareSupported();
|
||||
static bool callsSupported();
|
||||
static bool screenShareSupported();
|
||||
|
||||
public slots:
|
||||
void sendInvite(const QString &roomid, webrtc::CallType, unsigned int windowIndex = 0);
|
||||
void syncEvent(const mtx::events::collections::TimelineEvents &event);
|
||||
void toggleMicMute();
|
||||
void toggleLocalPiP() { session_.toggleLocalPiP(); }
|
||||
void acceptInvite();
|
||||
void hangUp(
|
||||
mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
|
||||
QStringList windowList();
|
||||
void previewWindow(unsigned int windowIndex) const;
|
||||
void sendInvite(const QString &roomid, webrtc::CallType, unsigned int windowIndex = 0);
|
||||
void syncEvent(const mtx::events::collections::TimelineEvents &event);
|
||||
void toggleMicMute();
|
||||
void toggleLocalPiP() { session_.toggleLocalPiP(); }
|
||||
void acceptInvite();
|
||||
void hangUp(mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
|
||||
QStringList windowList();
|
||||
void previewWindow(unsigned int windowIndex) const;
|
||||
|
||||
signals:
|
||||
void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
|
||||
void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
|
||||
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
|
||||
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
|
||||
void newInviteState();
|
||||
void newCallState();
|
||||
void micMuteChanged();
|
||||
void devicesChanged();
|
||||
void turnServerRetrieved(const mtx::responses::TurnServer &);
|
||||
void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
|
||||
void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
|
||||
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
|
||||
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
|
||||
void newInviteState();
|
||||
void newCallState();
|
||||
void micMuteChanged();
|
||||
void devicesChanged();
|
||||
void turnServerRetrieved(const mtx::responses::TurnServer &);
|
||||
|
||||
private slots:
|
||||
void retrieveTurnServer();
|
||||
void retrieveTurnServer();
|
||||
|
||||
private:
|
||||
WebRTCSession &session_;
|
||||
QString roomid_;
|
||||
QString callParty_;
|
||||
QString callPartyDisplayName_;
|
||||
QString callPartyAvatarUrl_;
|
||||
std::string callid_;
|
||||
const uint32_t timeoutms_ = 120000;
|
||||
webrtc::CallType callType_ = webrtc::CallType::VOICE;
|
||||
bool haveCallInvite_ = false;
|
||||
std::string inviteSDP_;
|
||||
std::vector<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_;
|
||||
std::vector<std::string> turnURIs_;
|
||||
QTimer turnServerTimer_;
|
||||
QMediaPlayer player_;
|
||||
std::vector<std::pair<QString, uint32_t>> windows_;
|
||||
WebRTCSession &session_;
|
||||
QString roomid_;
|
||||
QString callParty_;
|
||||
QString callPartyDisplayName_;
|
||||
QString callPartyAvatarUrl_;
|
||||
std::string callid_;
|
||||
const uint32_t timeoutms_ = 120000;
|
||||
webrtc::CallType callType_ = webrtc::CallType::VOICE;
|
||||
bool haveCallInvite_ = false;
|
||||
std::string inviteSDP_;
|
||||
std::vector<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_;
|
||||
std::vector<std::string> turnURIs_;
|
||||
QTimer turnServerTimer_;
|
||||
QMediaPlayer player_;
|
||||
std::vector<std::pair<QString, uint32_t>> windows_;
|
||||
|
||||
template<typename T>
|
||||
bool handleEvent(const mtx::events::collections::TimelineEvents &event);
|
||||
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &);
|
||||
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &);
|
||||
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &);
|
||||
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &);
|
||||
void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo);
|
||||
void generateCallID();
|
||||
QStringList devices(bool isVideo) const;
|
||||
void clear();
|
||||
void endCall();
|
||||
void playRingtone(const QUrl &ringtone, bool repeat);
|
||||
void stopRingtone();
|
||||
template<typename T>
|
||||
bool handleEvent(const mtx::events::collections::TimelineEvents &event);
|
||||
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &);
|
||||
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &);
|
||||
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &);
|
||||
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &);
|
||||
void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo);
|
||||
void generateCallID();
|
||||
QStringList devices(bool isVideo) const;
|
||||
void clear();
|
||||
void endCall();
|
||||
void playRingtone(const QUrl &ringtone, bool repeat);
|
||||
void stopRingtone();
|
||||
};
|
||||
|
|
|
|||
1925
src/ChatPage.cpp
1925
src/ChatPage.cpp
File diff suppressed because it is too large
Load diff
257
src/ChatPage.h
257
src/ChatPage.h
|
|
@ -52,186 +52,181 @@ using SecretsToDecrypt = std::map<std::string, mtx::secret_storage::AesHmacSha2E
|
|||
|
||||
class ChatPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
|
||||
ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
|
||||
|
||||
// Initialize all the components of the UI.
|
||||
void bootstrap(QString userid, QString homeserver, QString token);
|
||||
// Initialize all the components of the UI.
|
||||
void bootstrap(QString userid, QString homeserver, QString token);
|
||||
|
||||
static ChatPage *instance() { return instance_; }
|
||||
static ChatPage *instance() { return instance_; }
|
||||
|
||||
QSharedPointer<UserSettings> userSettings() { return userSettings_; }
|
||||
CallManager *callManager() { return callManager_; }
|
||||
TimelineViewManager *timelineManager() { return view_manager_; }
|
||||
void deleteConfigs();
|
||||
QSharedPointer<UserSettings> userSettings() { return userSettings_; }
|
||||
CallManager *callManager() { return callManager_; }
|
||||
TimelineViewManager *timelineManager() { return view_manager_; }
|
||||
void deleteConfigs();
|
||||
|
||||
void initiateLogout();
|
||||
void initiateLogout();
|
||||
|
||||
QString status() const;
|
||||
void setStatus(const QString &status);
|
||||
QString status() const;
|
||||
void setStatus(const QString &status);
|
||||
|
||||
mtx::presence::PresenceState currentPresence() const;
|
||||
mtx::presence::PresenceState currentPresence() const;
|
||||
|
||||
// TODO(Nico): Get rid of this!
|
||||
QString currentRoom() const;
|
||||
// TODO(Nico): Get rid of this!
|
||||
QString currentRoom() const;
|
||||
|
||||
public slots:
|
||||
bool handleMatrixUri(const QByteArray &uri);
|
||||
bool handleMatrixUri(const QUrl &uri);
|
||||
bool handleMatrixUri(const QByteArray &uri);
|
||||
bool handleMatrixUri(const QUrl &uri);
|
||||
|
||||
void startChat(QString userid);
|
||||
void leaveRoom(const QString &room_id);
|
||||
void createRoom(const mtx::requests::CreateRoom &req);
|
||||
void joinRoom(const QString &room);
|
||||
void joinRoomVia(const std::string &room_id,
|
||||
const std::vector<std::string> &via,
|
||||
bool promptForConfirmation = true);
|
||||
void startChat(QString userid);
|
||||
void leaveRoom(const QString &room_id);
|
||||
void createRoom(const mtx::requests::CreateRoom &req);
|
||||
void joinRoom(const QString &room);
|
||||
void joinRoomVia(const std::string &room_id,
|
||||
const std::vector<std::string> &via,
|
||||
bool promptForConfirmation = true);
|
||||
|
||||
void inviteUser(QString userid, QString reason);
|
||||
void kickUser(QString userid, QString reason);
|
||||
void banUser(QString userid, QString reason);
|
||||
void unbanUser(QString userid, QString reason);
|
||||
void inviteUser(QString userid, QString reason);
|
||||
void kickUser(QString userid, QString reason);
|
||||
void banUser(QString userid, QString reason);
|
||||
void unbanUser(QString userid, QString reason);
|
||||
|
||||
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
|
||||
void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
|
||||
const SecretsToDecrypt &secrets);
|
||||
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
|
||||
void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
|
||||
const SecretsToDecrypt &secrets);
|
||||
signals:
|
||||
void connectionLost();
|
||||
void connectionRestored();
|
||||
void connectionLost();
|
||||
void connectionRestored();
|
||||
|
||||
void notificationsRetrieved(const mtx::responses::Notifications &);
|
||||
void highlightedNotifsRetrieved(const mtx::responses::Notifications &,
|
||||
const QPoint widgetPos);
|
||||
void notificationsRetrieved(const mtx::responses::Notifications &);
|
||||
void highlightedNotifsRetrieved(const mtx::responses::Notifications &, const QPoint widgetPos);
|
||||
|
||||
void contentLoaded();
|
||||
void closing();
|
||||
void changeWindowTitle(const int);
|
||||
void unreadMessages(int count);
|
||||
void showNotification(const QString &msg);
|
||||
void showLoginPage(const QString &msg);
|
||||
void showUserSettingsPage();
|
||||
void showOverlayProgressBar();
|
||||
void contentLoaded();
|
||||
void closing();
|
||||
void changeWindowTitle(const int);
|
||||
void unreadMessages(int count);
|
||||
void showNotification(const QString &msg);
|
||||
void showLoginPage(const QString &msg);
|
||||
void showUserSettingsPage();
|
||||
void showOverlayProgressBar();
|
||||
|
||||
void ownProfileOk();
|
||||
void setUserDisplayName(const QString &name);
|
||||
void setUserAvatar(const QString &avatar);
|
||||
void loggedOut();
|
||||
void ownProfileOk();
|
||||
void setUserDisplayName(const QString &name);
|
||||
void setUserAvatar(const QString &avatar);
|
||||
void loggedOut();
|
||||
|
||||
void trySyncCb();
|
||||
void tryDelayedSyncCb();
|
||||
void tryInitialSyncCb();
|
||||
void newSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token);
|
||||
void leftRoom(const QString &room_id);
|
||||
void newRoom(const QString &room_id);
|
||||
void changeToRoom(const QString &room_id);
|
||||
void trySyncCb();
|
||||
void tryDelayedSyncCb();
|
||||
void tryInitialSyncCb();
|
||||
void newSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token);
|
||||
void leftRoom(const QString &room_id);
|
||||
void newRoom(const QString &room_id);
|
||||
void changeToRoom(const QString &room_id);
|
||||
|
||||
void initializeViews(const mtx::responses::Rooms &rooms);
|
||||
void initializeEmptyViews();
|
||||
void initializeMentions(const QMap<QString, mtx::responses::Notifications> ¬ifs);
|
||||
void syncUI(const mtx::responses::Rooms &rooms);
|
||||
void dropToLoginPageCb(const QString &msg);
|
||||
void initializeViews(const mtx::responses::Rooms &rooms);
|
||||
void initializeEmptyViews();
|
||||
void initializeMentions(const QMap<QString, mtx::responses::Notifications> ¬ifs);
|
||||
void syncUI(const mtx::responses::Rooms &rooms);
|
||||
void dropToLoginPageCb(const QString &msg);
|
||||
|
||||
void notifyMessage(const QString &roomid,
|
||||
const QString &eventid,
|
||||
const QString &roomname,
|
||||
const QString &sender,
|
||||
const QString &message,
|
||||
const QImage &icon);
|
||||
void notifyMessage(const QString &roomid,
|
||||
const QString &eventid,
|
||||
const QString &roomname,
|
||||
const QString &sender,
|
||||
const QString &message,
|
||||
const QImage &icon);
|
||||
|
||||
void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
|
||||
void themeChanged();
|
||||
void decryptSidebarChanged();
|
||||
void chatFocusChanged(const bool focused);
|
||||
void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
|
||||
void themeChanged();
|
||||
void decryptSidebarChanged();
|
||||
void chatFocusChanged(const bool focused);
|
||||
|
||||
//! Signals for device verificaiton
|
||||
void receivedDeviceVerificationAccept(
|
||||
const mtx::events::msg::KeyVerificationAccept &message);
|
||||
void receivedDeviceVerificationRequest(
|
||||
const mtx::events::msg::KeyVerificationRequest &message,
|
||||
std::string sender);
|
||||
void receivedRoomDeviceVerificationRequest(
|
||||
const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message,
|
||||
TimelineModel *model);
|
||||
void receivedDeviceVerificationCancel(
|
||||
const mtx::events::msg::KeyVerificationCancel &message);
|
||||
void receivedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message);
|
||||
void receivedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message);
|
||||
void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message,
|
||||
std::string sender);
|
||||
void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
|
||||
void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
|
||||
//! Signals for device verificaiton
|
||||
void receivedDeviceVerificationAccept(const mtx::events::msg::KeyVerificationAccept &message);
|
||||
void receivedDeviceVerificationRequest(const mtx::events::msg::KeyVerificationRequest &message,
|
||||
std::string sender);
|
||||
void receivedRoomDeviceVerificationRequest(
|
||||
const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message,
|
||||
TimelineModel *model);
|
||||
void receivedDeviceVerificationCancel(const mtx::events::msg::KeyVerificationCancel &message);
|
||||
void receivedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message);
|
||||
void receivedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message);
|
||||
void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message,
|
||||
std::string sender);
|
||||
void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
|
||||
void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
|
||||
|
||||
void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
|
||||
const SecretsToDecrypt &secrets);
|
||||
void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
|
||||
const SecretsToDecrypt &secrets);
|
||||
|
||||
private slots:
|
||||
void logout();
|
||||
void removeRoom(const QString &room_id);
|
||||
void changeRoom(const QString &room_id);
|
||||
void dropToLoginPage(const QString &msg);
|
||||
void logout();
|
||||
void removeRoom(const QString &room_id);
|
||||
void changeRoom(const QString &room_id);
|
||||
void dropToLoginPage(const QString &msg);
|
||||
|
||||
void handleSyncResponse(const mtx::responses::Sync &res,
|
||||
const std::string &prev_batch_token);
|
||||
void handleSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token);
|
||||
|
||||
private:
|
||||
static ChatPage *instance_;
|
||||
static ChatPage *instance_;
|
||||
|
||||
void startInitialSync();
|
||||
void tryInitialSync();
|
||||
void trySync();
|
||||
void verifyOneTimeKeyCountAfterStartup();
|
||||
void ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts);
|
||||
void getProfileInfo();
|
||||
void getBackupVersion();
|
||||
void startInitialSync();
|
||||
void tryInitialSync();
|
||||
void trySync();
|
||||
void verifyOneTimeKeyCountAfterStartup();
|
||||
void ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts);
|
||||
void getProfileInfo();
|
||||
void getBackupVersion();
|
||||
|
||||
//! Check if the given room is currently open.
|
||||
bool isRoomActive(const QString &room_id);
|
||||
//! Check if the given room is currently open.
|
||||
bool isRoomActive(const QString &room_id);
|
||||
|
||||
using UserID = QString;
|
||||
using Membership = mtx::events::StateEvent<mtx::events::state::Member>;
|
||||
using Memberships = std::map<std::string, Membership>;
|
||||
using UserID = QString;
|
||||
using Membership = mtx::events::StateEvent<mtx::events::state::Member>;
|
||||
using Memberships = std::map<std::string, Membership>;
|
||||
|
||||
void loadStateFromCache();
|
||||
void resetUI();
|
||||
void loadStateFromCache();
|
||||
void resetUI();
|
||||
|
||||
template<class Collection>
|
||||
Memberships getMemberships(const std::vector<Collection> &events) const;
|
||||
template<class Collection>
|
||||
Memberships getMemberships(const std::vector<Collection> &events) const;
|
||||
|
||||
//! Send desktop notification for the received messages.
|
||||
void sendNotifications(const mtx::responses::Notifications &);
|
||||
//! Send desktop notification for the received messages.
|
||||
void sendNotifications(const mtx::responses::Notifications &);
|
||||
|
||||
template<typename T>
|
||||
void connectCallMessage();
|
||||
template<typename T>
|
||||
void connectCallMessage();
|
||||
|
||||
QHBoxLayout *topLayout_;
|
||||
QHBoxLayout *topLayout_;
|
||||
|
||||
TimelineViewManager *view_manager_;
|
||||
TimelineViewManager *view_manager_;
|
||||
|
||||
QTimer connectivityTimer_;
|
||||
std::atomic_bool isConnected_;
|
||||
QTimer connectivityTimer_;
|
||||
std::atomic_bool isConnected_;
|
||||
|
||||
// Global user settings.
|
||||
QSharedPointer<UserSettings> userSettings_;
|
||||
// Global user settings.
|
||||
QSharedPointer<UserSettings> userSettings_;
|
||||
|
||||
NotificationsManager notificationsManager;
|
||||
CallManager *callManager_;
|
||||
NotificationsManager notificationsManager;
|
||||
CallManager *callManager_;
|
||||
};
|
||||
|
||||
template<class Collection>
|
||||
std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>>
|
||||
ChatPage::getMemberships(const std::vector<Collection> &collection) const
|
||||
{
|
||||
std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>> memberships;
|
||||
std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>> memberships;
|
||||
|
||||
using Member = mtx::events::StateEvent<mtx::events::state::Member>;
|
||||
using Member = mtx::events::StateEvent<mtx::events::state::Member>;
|
||||
|
||||
for (const auto &event : collection) {
|
||||
if (auto member = std::get_if<Member>(event)) {
|
||||
memberships.emplace(member->state_key, *member);
|
||||
}
|
||||
for (const auto &event : collection) {
|
||||
if (auto member = std::get_if<Member>(event)) {
|
||||
memberships.emplace(member->state_key, *member);
|
||||
}
|
||||
}
|
||||
|
||||
return memberships;
|
||||
return memberships;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,18 +10,17 @@
|
|||
Clipboard::Clipboard(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(
|
||||
QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &Clipboard::textChanged);
|
||||
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &Clipboard::textChanged);
|
||||
}
|
||||
|
||||
void
|
||||
Clipboard::setText(QString text)
|
||||
{
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
}
|
||||
|
||||
QString
|
||||
Clipboard::text() const
|
||||
{
|
||||
return QGuiApplication::clipboard()->text();
|
||||
return QGuiApplication::clipboard()->text();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@
|
|||
|
||||
class Clipboard : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
|
||||
|
||||
public:
|
||||
Clipboard(QObject *parent = nullptr);
|
||||
Clipboard(QObject *parent = nullptr);
|
||||
|
||||
QString text() const;
|
||||
void setText(QString text_);
|
||||
QString text() const;
|
||||
void setText(QString text_);
|
||||
signals:
|
||||
void textChanged();
|
||||
void textChanged();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,23 +9,23 @@
|
|||
QPixmap
|
||||
ColorImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &)
|
||||
{
|
||||
auto args = id.split('?');
|
||||
auto args = id.split('?');
|
||||
|
||||
QPixmap source(args[0]);
|
||||
QPixmap source(args[0]);
|
||||
|
||||
if (size)
|
||||
*size = QSize(source.width(), source.height());
|
||||
if (size)
|
||||
*size = QSize(source.width(), source.height());
|
||||
|
||||
if (args.size() < 2)
|
||||
return source;
|
||||
if (args.size() < 2)
|
||||
return source;
|
||||
|
||||
QColor color(args[1]);
|
||||
QColor color(args[1]);
|
||||
|
||||
QPixmap colorized = source;
|
||||
QPainter painter(&colorized);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
painter.fillRect(colorized.rect(), color);
|
||||
painter.end();
|
||||
QPixmap colorized = source;
|
||||
QPainter painter(&colorized);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
painter.fillRect(colorized.rect(), color);
|
||||
painter.end();
|
||||
|
||||
return colorized;
|
||||
return colorized;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
class ColorImageProvider : public QQuickImageProvider
|
||||
{
|
||||
public:
|
||||
ColorImageProvider()
|
||||
: QQuickImageProvider(QQuickImageProvider::Pixmap)
|
||||
{}
|
||||
ColorImageProvider()
|
||||
: QQuickImageProvider(QQuickImageProvider::Pixmap)
|
||||
{}
|
||||
|
||||
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
|
||||
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,65 +13,65 @@ CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId,
|
|||
: QAbstractListModel(parent)
|
||||
, room_id(roomId)
|
||||
{
|
||||
auto packs = cache::client()->getImagePacks(room_id, stickers);
|
||||
auto packs = cache::client()->getImagePacks(room_id, stickers);
|
||||
|
||||
for (const auto &pack : packs) {
|
||||
QString packname =
|
||||
pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : "";
|
||||
for (const auto &pack : packs) {
|
||||
QString packname =
|
||||
pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : "";
|
||||
|
||||
for (const auto &img : pack.pack.images) {
|
||||
ImageDesc i{};
|
||||
i.shortcode = QString::fromStdString(img.first);
|
||||
i.packname = packname;
|
||||
i.image = img.second;
|
||||
images.push_back(std::move(i));
|
||||
}
|
||||
for (const auto &img : pack.pack.images) {
|
||||
ImageDesc i{};
|
||||
i.shortcode = QString::fromStdString(img.first);
|
||||
i.packname = packname;
|
||||
i.image = img.second;
|
||||
images.push_back(std::move(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
CombinedImagePackModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return (int)images.size();
|
||||
return (int)images.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
CombinedImagePackModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{CompletionModel::CompletionRole, "completionRole"},
|
||||
{CompletionModel::SearchRole, "searchRole"},
|
||||
{CompletionModel::SearchRole2, "searchRole2"},
|
||||
{Roles::Url, "url"},
|
||||
{Roles::ShortCode, "shortcode"},
|
||||
{Roles::Body, "body"},
|
||||
{Roles::PackName, "packname"},
|
||||
{Roles::OriginalRow, "originalRow"},
|
||||
};
|
||||
return {
|
||||
{CompletionModel::CompletionRole, "completionRole"},
|
||||
{CompletionModel::SearchRole, "searchRole"},
|
||||
{CompletionModel::SearchRole2, "searchRole2"},
|
||||
{Roles::Url, "url"},
|
||||
{Roles::ShortCode, "shortcode"},
|
||||
{Roles::Body, "body"},
|
||||
{Roles::PackName, "packname"},
|
||||
{Roles::OriginalRow, "originalRow"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
CombinedImagePackModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
switch (role) {
|
||||
case CompletionModel::CompletionRole:
|
||||
return QString::fromStdString(images[index.row()].image.url);
|
||||
case Roles::Url:
|
||||
return QString::fromStdString(images[index.row()].image.url);
|
||||
case CompletionModel::SearchRole:
|
||||
case Roles::ShortCode:
|
||||
return images[index.row()].shortcode;
|
||||
case CompletionModel::SearchRole2:
|
||||
case Roles::Body:
|
||||
return QString::fromStdString(images[index.row()].image.body);
|
||||
case Roles::PackName:
|
||||
return images[index.row()].packname;
|
||||
case Roles::OriginalRow:
|
||||
return index.row();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
switch (role) {
|
||||
case CompletionModel::CompletionRole:
|
||||
return QString::fromStdString(images[index.row()].image.url);
|
||||
case Roles::Url:
|
||||
return QString::fromStdString(images[index.row()].image.url);
|
||||
case CompletionModel::SearchRole:
|
||||
case Roles::ShortCode:
|
||||
return images[index.row()].shortcode;
|
||||
case CompletionModel::SearchRole2:
|
||||
case Roles::Body:
|
||||
return QString::fromStdString(images[index.row()].image.body);
|
||||
case Roles::PackName:
|
||||
return images[index.row()].packname;
|
||||
case Roles::OriginalRow:
|
||||
return index.row();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,39 +10,39 @@
|
|||
|
||||
class CombinedImagePackModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
Url = Qt::UserRole,
|
||||
ShortCode,
|
||||
Body,
|
||||
PackName,
|
||||
OriginalRow,
|
||||
};
|
||||
enum Roles
|
||||
{
|
||||
Url = Qt::UserRole,
|
||||
ShortCode,
|
||||
Body,
|
||||
PackName,
|
||||
OriginalRow,
|
||||
};
|
||||
|
||||
CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
mtx::events::msc2545::PackImage imageAt(int row)
|
||||
{
|
||||
if (row < 0 || static_cast<size_t>(row) >= images.size())
|
||||
return {};
|
||||
return images.at(static_cast<size_t>(row)).image;
|
||||
}
|
||||
mtx::events::msc2545::PackImage imageAt(int row)
|
||||
{
|
||||
if (row < 0 || static_cast<size_t>(row) >= images.size())
|
||||
return {};
|
||||
return images.at(static_cast<size_t>(row)).image;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string room_id;
|
||||
std::string room_id;
|
||||
|
||||
struct ImageDesc
|
||||
{
|
||||
QString shortcode;
|
||||
QString packname;
|
||||
struct ImageDesc
|
||||
{
|
||||
QString shortcode;
|
||||
QString packname;
|
||||
|
||||
mtx::events::msc2545::PackImage image;
|
||||
};
|
||||
mtx::events::msc2545::PackImage image;
|
||||
};
|
||||
|
||||
std::vector<ImageDesc> images;
|
||||
std::vector<ImageDesc> images;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ namespace CompletionModel {
|
|||
// Start at Qt::UserRole * 2 to prevent clashes
|
||||
enum Roles
|
||||
{
|
||||
CompletionRole = Qt::UserRole * 2, // The string to replace the active completion
|
||||
SearchRole, // String completer uses for search
|
||||
SearchRole2, // Secondary string completer uses for search
|
||||
CompletionRole = Qt::UserRole * 2, // The string to replace the active completion
|
||||
SearchRole, // String completer uses for search
|
||||
SearchRole2, // Secondary string completer uses for search
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,154 +18,154 @@ CompletionProxyModel::CompletionProxyModel(QAbstractItemModel *model,
|
|||
, maxMistakes_(max_mistakes)
|
||||
, max_completions_(max_completions)
|
||||
{
|
||||
setSourceModel(model);
|
||||
QChar splitPoints(' ');
|
||||
setSourceModel(model);
|
||||
QChar splitPoints(' ');
|
||||
|
||||
// insert all the full texts
|
||||
for (int i = 0; i < sourceModel()->rowCount(); i++) {
|
||||
if (static_cast<size_t>(i) < max_completions_)
|
||||
mapping.push_back(i);
|
||||
// insert all the full texts
|
||||
for (int i = 0; i < sourceModel()->rowCount(); i++) {
|
||||
if (static_cast<size_t>(i) < max_completions_)
|
||||
mapping.push_back(i);
|
||||
|
||||
auto string1 = sourceModel()
|
||||
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
|
||||
.toString()
|
||||
.toLower();
|
||||
if (!string1.isEmpty())
|
||||
trie_.insert(string1.toUcs4(), i);
|
||||
auto string1 = sourceModel()
|
||||
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
|
||||
.toString()
|
||||
.toLower();
|
||||
if (!string1.isEmpty())
|
||||
trie_.insert(string1.toUcs4(), i);
|
||||
|
||||
auto string2 = sourceModel()
|
||||
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
|
||||
.toString()
|
||||
.toLower();
|
||||
if (!string2.isEmpty())
|
||||
trie_.insert(string2.toUcs4(), i);
|
||||
auto string2 = sourceModel()
|
||||
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
|
||||
.toString()
|
||||
.toLower();
|
||||
if (!string2.isEmpty())
|
||||
trie_.insert(string2.toUcs4(), i);
|
||||
}
|
||||
|
||||
// insert the partial matches
|
||||
for (int i = 0; i < sourceModel()->rowCount(); i++) {
|
||||
auto string1 = sourceModel()
|
||||
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
|
||||
.toString()
|
||||
.toLower();
|
||||
|
||||
for (const auto &e : string1.splitRef(splitPoints)) {
|
||||
if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
|
||||
trie_.insert(e.toUcs4(), i);
|
||||
}
|
||||
|
||||
// insert the partial matches
|
||||
for (int i = 0; i < sourceModel()->rowCount(); i++) {
|
||||
auto string1 = sourceModel()
|
||||
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
|
||||
.toString()
|
||||
.toLower();
|
||||
auto string2 = sourceModel()
|
||||
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
|
||||
.toString()
|
||||
.toLower();
|
||||
|
||||
for (const auto &e : string1.splitRef(splitPoints)) {
|
||||
if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
|
||||
trie_.insert(e.toUcs4(), i);
|
||||
}
|
||||
|
||||
auto string2 = sourceModel()
|
||||
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
|
||||
.toString()
|
||||
.toLower();
|
||||
|
||||
if (!string2.isEmpty()) {
|
||||
for (const auto &e : string2.splitRef(splitPoints)) {
|
||||
if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
|
||||
trie_.insert(e.toUcs4(), i);
|
||||
}
|
||||
}
|
||||
if (!string2.isEmpty()) {
|
||||
for (const auto &e : string2.splitRef(splitPoints)) {
|
||||
if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
|
||||
trie_.insert(e.toUcs4(), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connect(
|
||||
this,
|
||||
&CompletionProxyModel::newSearchString,
|
||||
this,
|
||||
[this](QString s) {
|
||||
s.remove(":");
|
||||
s.remove("@");
|
||||
searchString_ = s.toLower();
|
||||
invalidate();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
connect(
|
||||
this,
|
||||
&CompletionProxyModel::newSearchString,
|
||||
this,
|
||||
[this](QString s) {
|
||||
s.remove(":");
|
||||
s.remove("@");
|
||||
searchString_ = s.toLower();
|
||||
invalidate();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void
|
||||
CompletionProxyModel::invalidate()
|
||||
{
|
||||
auto key = searchString_.toUcs4();
|
||||
beginResetModel();
|
||||
if (!key.empty()) // return default model data, if no search string
|
||||
mapping = trie_.search(key, max_completions_, maxMistakes_);
|
||||
endResetModel();
|
||||
auto key = searchString_.toUcs4();
|
||||
beginResetModel();
|
||||
if (!key.empty()) // return default model data, if no search string
|
||||
mapping = trie_.search(key, max_completions_, maxMistakes_);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
CompletionProxyModel::roleNames() const
|
||||
{
|
||||
return this->sourceModel()->roleNames();
|
||||
return this->sourceModel()->roleNames();
|
||||
}
|
||||
|
||||
int
|
||||
CompletionProxyModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
if (searchString_.isEmpty())
|
||||
return std::min(static_cast<int>(std::min<size_t>(max_completions_,
|
||||
std::numeric_limits<int>::max())),
|
||||
sourceModel()->rowCount());
|
||||
else
|
||||
return (int)mapping.size();
|
||||
if (searchString_.isEmpty())
|
||||
return std::min(
|
||||
static_cast<int>(std::min<size_t>(max_completions_, std::numeric_limits<int>::max())),
|
||||
sourceModel()->rowCount());
|
||||
else
|
||||
return (int)mapping.size();
|
||||
}
|
||||
|
||||
QModelIndex
|
||||
CompletionProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
|
||||
{
|
||||
// return default model data, if no search string
|
||||
if (searchString_.isEmpty()) {
|
||||
return index(sourceIndex.row(), 0);
|
||||
}
|
||||
// return default model data, if no search string
|
||||
if (searchString_.isEmpty()) {
|
||||
return index(sourceIndex.row(), 0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)mapping.size(); i++) {
|
||||
if (mapping[i] == sourceIndex.row()) {
|
||||
return index(i, 0);
|
||||
}
|
||||
for (int i = 0; i < (int)mapping.size(); i++) {
|
||||
if (mapping[i] == sourceIndex.row()) {
|
||||
return index(i, 0);
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex
|
||||
CompletionProxyModel::mapToSource(const QModelIndex &proxyIndex) const
|
||||
{
|
||||
auto row = proxyIndex.row();
|
||||
auto row = proxyIndex.row();
|
||||
|
||||
// return default model data, if no search string
|
||||
if (searchString_.isEmpty()) {
|
||||
return index(row, 0);
|
||||
}
|
||||
// return default model data, if no search string
|
||||
if (searchString_.isEmpty()) {
|
||||
return index(row, 0);
|
||||
}
|
||||
|
||||
if (row < 0 || row >= (int)mapping.size())
|
||||
return QModelIndex();
|
||||
if (row < 0 || row >= (int)mapping.size())
|
||||
return QModelIndex();
|
||||
|
||||
return sourceModel()->index(mapping[row], 0);
|
||||
return sourceModel()->index(mapping[row], 0);
|
||||
}
|
||||
|
||||
QModelIndex
|
||||
CompletionProxyModel::index(int row, int column, const QModelIndex &) const
|
||||
{
|
||||
return createIndex(row, column);
|
||||
return createIndex(row, column);
|
||||
}
|
||||
|
||||
QModelIndex
|
||||
CompletionProxyModel::parent(const QModelIndex &) const
|
||||
{
|
||||
return QModelIndex{};
|
||||
return QModelIndex{};
|
||||
}
|
||||
int
|
||||
CompletionProxyModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return sourceModel()->columnCount();
|
||||
return sourceModel()->columnCount();
|
||||
}
|
||||
|
||||
QVariant
|
||||
CompletionProxyModel::completionAt(int i) const
|
||||
{
|
||||
if (i >= 0 && i < rowCount())
|
||||
return data(index(i, 0), CompletionModel::CompletionRole);
|
||||
else
|
||||
return {};
|
||||
if (i >= 0 && i < rowCount())
|
||||
return data(index(i, 0), CompletionModel::CompletionRole);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
CompletionProxyModel::setSearchString(QString s)
|
||||
{
|
||||
emit newSearchString(s);
|
||||
emit newSearchString(s);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,179 +11,176 @@
|
|||
template<typename Key, typename Value>
|
||||
struct trie
|
||||
{
|
||||
std::vector<Value> values;
|
||||
std::map<Key, trie> next;
|
||||
std::vector<Value> values;
|
||||
std::map<Key, trie> next;
|
||||
|
||||
void insert(const QVector<Key> &keys, const Value &v)
|
||||
{
|
||||
auto t = this;
|
||||
for (const auto k : keys) {
|
||||
t = &t->next[k];
|
||||
}
|
||||
|
||||
t->values.push_back(v);
|
||||
void insert(const QVector<Key> &keys, const Value &v)
|
||||
{
|
||||
auto t = this;
|
||||
for (const auto k : keys) {
|
||||
t = &t->next[k];
|
||||
}
|
||||
|
||||
std::vector<Value> valuesAndSubvalues(size_t limit = -1) const
|
||||
{
|
||||
std::vector<Value> ret;
|
||||
if (limit < 200)
|
||||
ret.reserve(limit);
|
||||
t->values.push_back(v);
|
||||
}
|
||||
|
||||
for (const auto &v : values) {
|
||||
if (ret.size() >= limit)
|
||||
return ret;
|
||||
else
|
||||
ret.push_back(v);
|
||||
}
|
||||
|
||||
for (const auto &[k, t] : next) {
|
||||
(void)k;
|
||||
if (ret.size() >= limit)
|
||||
return ret;
|
||||
else {
|
||||
auto temp = t.valuesAndSubvalues(limit - ret.size());
|
||||
for (auto &&v : temp) {
|
||||
if (ret.size() >= limit)
|
||||
return ret;
|
||||
|
||||
if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
|
||||
ret.push_back(std::move(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<Value> valuesAndSubvalues(size_t limit = -1) const
|
||||
{
|
||||
std::vector<Value> ret;
|
||||
if (limit < 200)
|
||||
ret.reserve(limit);
|
||||
|
||||
for (const auto &v : values) {
|
||||
if (ret.size() >= limit)
|
||||
return ret;
|
||||
else
|
||||
ret.push_back(v);
|
||||
}
|
||||
|
||||
std::vector<Value> search(const QVector<Key> &keys, //< TODO(Nico): replace this with a span
|
||||
size_t result_count_limit,
|
||||
size_t max_edit_distance_ = 2) const
|
||||
{
|
||||
std::vector<Value> ret;
|
||||
if (!result_count_limit)
|
||||
for (const auto &[k, t] : next) {
|
||||
(void)k;
|
||||
if (ret.size() >= limit)
|
||||
return ret;
|
||||
else {
|
||||
auto temp = t.valuesAndSubvalues(limit - ret.size());
|
||||
for (auto &&v : temp) {
|
||||
if (ret.size() >= limit)
|
||||
return ret;
|
||||
|
||||
if (keys.isEmpty())
|
||||
return valuesAndSubvalues(result_count_limit);
|
||||
if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
|
||||
ret.push_back(std::move(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto append = [&ret, result_count_limit](std::vector<Value> &&in) {
|
||||
for (auto &&v : in) {
|
||||
if (ret.size() >= result_count_limit)
|
||||
return;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
|
||||
ret.push_back(std::move(v));
|
||||
}
|
||||
std::vector<Value> search(const QVector<Key> &keys, //< TODO(Nico): replace this with a span
|
||||
size_t result_count_limit,
|
||||
size_t max_edit_distance_ = 2) const
|
||||
{
|
||||
std::vector<Value> ret;
|
||||
if (!result_count_limit)
|
||||
return ret;
|
||||
|
||||
if (keys.isEmpty())
|
||||
return valuesAndSubvalues(result_count_limit);
|
||||
|
||||
auto append = [&ret, result_count_limit](std::vector<Value> &&in) {
|
||||
for (auto &&v : in) {
|
||||
if (ret.size() >= result_count_limit)
|
||||
return;
|
||||
|
||||
if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
|
||||
ret.push_back(std::move(v));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto limit = [&ret, result_count_limit] {
|
||||
return std::min(result_count_limit, (result_count_limit - ret.size()) * 2);
|
||||
};
|
||||
|
||||
// Try first exact matches, then with maximum errors
|
||||
for (size_t max_edit_distance = 0;
|
||||
max_edit_distance <= max_edit_distance_ && ret.size() < result_count_limit;
|
||||
max_edit_distance += 1) {
|
||||
if (max_edit_distance && ret.size() < result_count_limit) {
|
||||
max_edit_distance -= 1;
|
||||
|
||||
// swap chars case
|
||||
if (keys.size() >= 2) {
|
||||
auto t = this;
|
||||
for (int i = 1; i >= 0; i--) {
|
||||
if (auto e = t->next.find(keys[i]); e != t->next.end()) {
|
||||
t = &e->second;
|
||||
} else {
|
||||
t = nullptr;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
auto limit = [&ret, result_count_limit] {
|
||||
return std::min(result_count_limit, (result_count_limit - ret.size()) * 2);
|
||||
};
|
||||
|
||||
// Try first exact matches, then with maximum errors
|
||||
for (size_t max_edit_distance = 0;
|
||||
max_edit_distance <= max_edit_distance_ && ret.size() < result_count_limit;
|
||||
max_edit_distance += 1) {
|
||||
if (max_edit_distance && ret.size() < result_count_limit) {
|
||||
max_edit_distance -= 1;
|
||||
|
||||
// swap chars case
|
||||
if (keys.size() >= 2) {
|
||||
auto t = this;
|
||||
for (int i = 1; i >= 0; i--) {
|
||||
if (auto e = t->next.find(keys[i]);
|
||||
e != t->next.end()) {
|
||||
t = &e->second;
|
||||
} else {
|
||||
t = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (t) {
|
||||
append(t->search(
|
||||
keys.mid(2), limit(), max_edit_distance));
|
||||
}
|
||||
}
|
||||
|
||||
// insert case
|
||||
for (const auto &[k, t] : this->next) {
|
||||
if (k == keys[0])
|
||||
continue;
|
||||
if (ret.size() >= limit())
|
||||
break;
|
||||
|
||||
// insert
|
||||
append(t.search(keys, limit(), max_edit_distance));
|
||||
}
|
||||
|
||||
// delete character case
|
||||
append(this->search(keys.mid(1), limit(), max_edit_distance));
|
||||
|
||||
// substitute case
|
||||
for (const auto &[k, t] : this->next) {
|
||||
if (k == keys[0])
|
||||
continue;
|
||||
if (ret.size() >= limit())
|
||||
break;
|
||||
|
||||
// substitute
|
||||
append(t.search(keys.mid(1), limit(), max_edit_distance));
|
||||
}
|
||||
|
||||
max_edit_distance += 1;
|
||||
}
|
||||
|
||||
if (auto e = this->next.find(keys[0]); e != this->next.end()) {
|
||||
append(e->second.search(keys.mid(1), limit(), max_edit_distance));
|
||||
}
|
||||
if (t) {
|
||||
append(t->search(keys.mid(2), limit(), max_edit_distance));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
// insert case
|
||||
for (const auto &[k, t] : this->next) {
|
||||
if (k == keys[0])
|
||||
continue;
|
||||
if (ret.size() >= limit())
|
||||
break;
|
||||
|
||||
// insert
|
||||
append(t.search(keys, limit(), max_edit_distance));
|
||||
}
|
||||
|
||||
// delete character case
|
||||
append(this->search(keys.mid(1), limit(), max_edit_distance));
|
||||
|
||||
// substitute case
|
||||
for (const auto &[k, t] : this->next) {
|
||||
if (k == keys[0])
|
||||
continue;
|
||||
if (ret.size() >= limit())
|
||||
break;
|
||||
|
||||
// substitute
|
||||
append(t.search(keys.mid(1), limit(), max_edit_distance));
|
||||
}
|
||||
|
||||
max_edit_distance += 1;
|
||||
}
|
||||
|
||||
if (auto e = this->next.find(keys[0]); e != this->next.end()) {
|
||||
append(e->second.search(keys.mid(1), limit(), max_edit_distance));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class CompletionProxyModel : public QAbstractProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(
|
||||
QString searchString READ searchString WRITE setSearchString NOTIFY newSearchString)
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY newSearchString)
|
||||
public:
|
||||
CompletionProxyModel(QAbstractItemModel *model,
|
||||
int max_mistakes = 2,
|
||||
size_t max_completions = 7,
|
||||
QObject *parent = nullptr);
|
||||
CompletionProxyModel(QAbstractItemModel *model,
|
||||
int max_mistakes = 2,
|
||||
size_t max_completions = 7,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
void invalidate();
|
||||
void invalidate();
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &) const override;
|
||||
|
||||
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
|
||||
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
|
||||
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
|
||||
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
|
||||
|
||||
QModelIndex index(int row,
|
||||
int column,
|
||||
const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex &) const override;
|
||||
QModelIndex index(int row,
|
||||
int column,
|
||||
const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex &) const override;
|
||||
|
||||
public slots:
|
||||
QVariant completionAt(int i) const;
|
||||
QVariant completionAt(int i) const;
|
||||
|
||||
void setSearchString(QString s);
|
||||
QString searchString() const { return searchString_; }
|
||||
void setSearchString(QString s);
|
||||
QString searchString() const { return searchString_; }
|
||||
|
||||
signals:
|
||||
void newSearchString(QString);
|
||||
void newSearchString(QString);
|
||||
|
||||
private:
|
||||
QString searchString_;
|
||||
trie<uint, int> trie_;
|
||||
std::vector<int> mapping;
|
||||
int maxMistakes_;
|
||||
size_t max_completions_;
|
||||
QString searchString_;
|
||||
trie<uint, int> trie_;
|
||||
std::vector<int> mapping;
|
||||
int maxMistakes_;
|
||||
size_t max_completions_;
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -60,192 +60,189 @@ using sas_ptr = std::unique_ptr<mtx::crypto::SAS>;
|
|||
// clang-format on
|
||||
class DeviceVerificationFlow : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString state READ state NOTIFY stateChanged)
|
||||
Q_PROPERTY(Error error READ error NOTIFY errorChanged)
|
||||
Q_PROPERTY(QString userId READ getUserId CONSTANT)
|
||||
Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT)
|
||||
Q_PROPERTY(bool sender READ getSender CONSTANT)
|
||||
Q_PROPERTY(std::vector<int> sasList READ getSasList CONSTANT)
|
||||
Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT)
|
||||
Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT)
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString state READ state NOTIFY stateChanged)
|
||||
Q_PROPERTY(Error error READ error NOTIFY errorChanged)
|
||||
Q_PROPERTY(QString userId READ getUserId CONSTANT)
|
||||
Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT)
|
||||
Q_PROPERTY(bool sender READ getSender CONSTANT)
|
||||
Q_PROPERTY(std::vector<int> sasList READ getSasList CONSTANT)
|
||||
Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT)
|
||||
Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT)
|
||||
|
||||
public:
|
||||
enum State
|
||||
{
|
||||
PromptStartVerification,
|
||||
WaitingForOtherToAccept,
|
||||
WaitingForKeys,
|
||||
CompareEmoji,
|
||||
CompareNumber,
|
||||
WaitingForMac,
|
||||
Success,
|
||||
Failed,
|
||||
};
|
||||
Q_ENUM(State)
|
||||
enum State
|
||||
{
|
||||
PromptStartVerification,
|
||||
WaitingForOtherToAccept,
|
||||
WaitingForKeys,
|
||||
CompareEmoji,
|
||||
CompareNumber,
|
||||
WaitingForMac,
|
||||
Success,
|
||||
Failed,
|
||||
};
|
||||
Q_ENUM(State)
|
||||
|
||||
enum Type
|
||||
{
|
||||
ToDevice,
|
||||
RoomMsg
|
||||
};
|
||||
enum Type
|
||||
{
|
||||
ToDevice,
|
||||
RoomMsg
|
||||
};
|
||||
|
||||
enum Error
|
||||
{
|
||||
UnknownMethod,
|
||||
MismatchedCommitment,
|
||||
MismatchedSAS,
|
||||
KeyMismatch,
|
||||
Timeout,
|
||||
User,
|
||||
OutOfOrder,
|
||||
};
|
||||
Q_ENUM(Error)
|
||||
enum Error
|
||||
{
|
||||
UnknownMethod,
|
||||
MismatchedCommitment,
|
||||
MismatchedSAS,
|
||||
KeyMismatch,
|
||||
Timeout,
|
||||
User,
|
||||
OutOfOrder,
|
||||
};
|
||||
Q_ENUM(Error)
|
||||
|
||||
static QSharedPointer<DeviceVerificationFlow> NewInRoomVerification(
|
||||
QObject *parent_,
|
||||
TimelineModel *timelineModel_,
|
||||
const mtx::events::msg::KeyVerificationRequest &msg,
|
||||
QString other_user_,
|
||||
QString event_id_);
|
||||
static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
|
||||
QObject *parent_,
|
||||
const mtx::events::msg::KeyVerificationRequest &msg,
|
||||
QString other_user_,
|
||||
QString txn_id_);
|
||||
static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
|
||||
QObject *parent_,
|
||||
const mtx::events::msg::KeyVerificationStart &msg,
|
||||
QString other_user_,
|
||||
QString txn_id_);
|
||||
static QSharedPointer<DeviceVerificationFlow>
|
||||
InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid);
|
||||
static QSharedPointer<DeviceVerificationFlow> InitiateDeviceVerification(QObject *parent,
|
||||
QString userid,
|
||||
QString device);
|
||||
static QSharedPointer<DeviceVerificationFlow> NewInRoomVerification(
|
||||
QObject *parent_,
|
||||
TimelineModel *timelineModel_,
|
||||
const mtx::events::msg::KeyVerificationRequest &msg,
|
||||
QString other_user_,
|
||||
QString event_id_);
|
||||
static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
|
||||
QObject *parent_,
|
||||
const mtx::events::msg::KeyVerificationRequest &msg,
|
||||
QString other_user_,
|
||||
QString txn_id_);
|
||||
static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
|
||||
QObject *parent_,
|
||||
const mtx::events::msg::KeyVerificationStart &msg,
|
||||
QString other_user_,
|
||||
QString txn_id_);
|
||||
static QSharedPointer<DeviceVerificationFlow>
|
||||
InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid);
|
||||
static QSharedPointer<DeviceVerificationFlow> InitiateDeviceVerification(QObject *parent,
|
||||
QString userid,
|
||||
QString device);
|
||||
|
||||
// getters
|
||||
QString state();
|
||||
Error error() { return error_; }
|
||||
QString getUserId();
|
||||
QString getDeviceId();
|
||||
bool getSender();
|
||||
std::vector<int> getSasList();
|
||||
QString transactionId() { return QString::fromStdString(this->transaction_id); }
|
||||
// setters
|
||||
void setDeviceId(QString deviceID);
|
||||
void setEventId(std::string event_id);
|
||||
bool isDeviceVerification() const
|
||||
{
|
||||
return this->type == DeviceVerificationFlow::Type::ToDevice;
|
||||
}
|
||||
bool isSelfVerification() const;
|
||||
// getters
|
||||
QString state();
|
||||
Error error() { return error_; }
|
||||
QString getUserId();
|
||||
QString getDeviceId();
|
||||
bool getSender();
|
||||
std::vector<int> getSasList();
|
||||
QString transactionId() { return QString::fromStdString(this->transaction_id); }
|
||||
// setters
|
||||
void setDeviceId(QString deviceID);
|
||||
void setEventId(std::string event_id);
|
||||
bool isDeviceVerification() const
|
||||
{
|
||||
return this->type == DeviceVerificationFlow::Type::ToDevice;
|
||||
}
|
||||
bool isSelfVerification() const;
|
||||
|
||||
void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id);
|
||||
void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id);
|
||||
|
||||
public slots:
|
||||
//! unverifies a device
|
||||
void unverify();
|
||||
//! Continues the flow
|
||||
void next();
|
||||
//! Cancel the flow
|
||||
void cancel() { cancelVerification(User); }
|
||||
//! unverifies a device
|
||||
void unverify();
|
||||
//! Continues the flow
|
||||
void next();
|
||||
//! Cancel the flow
|
||||
void cancel() { cancelVerification(User); }
|
||||
|
||||
signals:
|
||||
void refreshProfile();
|
||||
void stateChanged();
|
||||
void errorChanged();
|
||||
void refreshProfile();
|
||||
void stateChanged();
|
||||
void errorChanged();
|
||||
|
||||
private:
|
||||
DeviceVerificationFlow(QObject *,
|
||||
DeviceVerificationFlow::Type flow_type,
|
||||
TimelineModel *model,
|
||||
QString userID,
|
||||
QString deviceId_);
|
||||
void setState(State state)
|
||||
{
|
||||
if (state != state_) {
|
||||
state_ = state;
|
||||
emit stateChanged();
|
||||
}
|
||||
DeviceVerificationFlow(QObject *,
|
||||
DeviceVerificationFlow::Type flow_type,
|
||||
TimelineModel *model,
|
||||
QString userID,
|
||||
QString deviceId_);
|
||||
void setState(State state)
|
||||
{
|
||||
if (state != state_) {
|
||||
state_ = state;
|
||||
emit stateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string);
|
||||
//! sends a verification request
|
||||
void sendVerificationRequest();
|
||||
//! accepts a verification request
|
||||
void sendVerificationReady();
|
||||
//! completes the verification flow();
|
||||
void sendVerificationDone();
|
||||
//! accepts a verification
|
||||
void acceptVerificationRequest();
|
||||
//! starts the verification flow
|
||||
void startVerificationRequest();
|
||||
//! cancels a verification flow
|
||||
void cancelVerification(DeviceVerificationFlow::Error error_code);
|
||||
//! sends the verification key
|
||||
void sendVerificationKey();
|
||||
//! sends the mac of the keys
|
||||
void sendVerificationMac();
|
||||
//! Completes the verification flow
|
||||
void acceptDevice();
|
||||
|
||||
std::string transaction_id;
|
||||
|
||||
bool sender;
|
||||
Type type;
|
||||
mtx::identifiers::User toClient;
|
||||
QString deviceId;
|
||||
|
||||
// public part of our master key, when trusted or empty
|
||||
std::string our_trusted_master_key;
|
||||
|
||||
mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji;
|
||||
QTimer *timeout = nullptr;
|
||||
sas_ptr sas;
|
||||
std::string mac_method;
|
||||
std::string commitment;
|
||||
nlohmann::json canonical_json;
|
||||
|
||||
std::vector<int> sasList;
|
||||
UserKeyCache their_keys;
|
||||
TimelineModel *model_;
|
||||
mtx::common::Relation relation;
|
||||
|
||||
State state_ = PromptStartVerification;
|
||||
Error error_ = UnknownMethod;
|
||||
|
||||
bool isMacVerified = false;
|
||||
|
||||
template<typename T>
|
||||
void send(T msg)
|
||||
{
|
||||
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
|
||||
mtx::requests::ToDeviceMessages<T> body;
|
||||
msg.transaction_id = this->transaction_id;
|
||||
body[this->toClient][deviceId.toStdString()] = msg;
|
||||
|
||||
http::client()->send_to_device<T>(
|
||||
this->transaction_id, body, [](mtx::http::RequestErr err) {
|
||||
if (err)
|
||||
nhlog::net()->warn("failed to send verification to_device message: {} {}",
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
});
|
||||
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
|
||||
if constexpr (!std::is_same_v<T, mtx::events::msg::KeyVerificationRequest>) {
|
||||
msg.relations.relations.push_back(this->relation);
|
||||
// Set synthesized to surpress the nheko relation extensions
|
||||
msg.relations.synthesized = true;
|
||||
}
|
||||
(model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type<T>);
|
||||
}
|
||||
|
||||
void handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string);
|
||||
//! sends a verification request
|
||||
void sendVerificationRequest();
|
||||
//! accepts a verification request
|
||||
void sendVerificationReady();
|
||||
//! completes the verification flow();
|
||||
void sendVerificationDone();
|
||||
//! accepts a verification
|
||||
void acceptVerificationRequest();
|
||||
//! starts the verification flow
|
||||
void startVerificationRequest();
|
||||
//! cancels a verification flow
|
||||
void cancelVerification(DeviceVerificationFlow::Error error_code);
|
||||
//! sends the verification key
|
||||
void sendVerificationKey();
|
||||
//! sends the mac of the keys
|
||||
void sendVerificationMac();
|
||||
//! Completes the verification flow
|
||||
void acceptDevice();
|
||||
|
||||
std::string transaction_id;
|
||||
|
||||
bool sender;
|
||||
Type type;
|
||||
mtx::identifiers::User toClient;
|
||||
QString deviceId;
|
||||
|
||||
// public part of our master key, when trusted or empty
|
||||
std::string our_trusted_master_key;
|
||||
|
||||
mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji;
|
||||
QTimer *timeout = nullptr;
|
||||
sas_ptr sas;
|
||||
std::string mac_method;
|
||||
std::string commitment;
|
||||
nlohmann::json canonical_json;
|
||||
|
||||
std::vector<int> sasList;
|
||||
UserKeyCache their_keys;
|
||||
TimelineModel *model_;
|
||||
mtx::common::Relation relation;
|
||||
|
||||
State state_ = PromptStartVerification;
|
||||
Error error_ = UnknownMethod;
|
||||
|
||||
bool isMacVerified = false;
|
||||
|
||||
template<typename T>
|
||||
void send(T msg)
|
||||
{
|
||||
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
|
||||
mtx::requests::ToDeviceMessages<T> body;
|
||||
msg.transaction_id = this->transaction_id;
|
||||
body[this->toClient][deviceId.toStdString()] = msg;
|
||||
|
||||
http::client()->send_to_device<T>(
|
||||
this->transaction_id, body, [](mtx::http::RequestErr err) {
|
||||
if (err)
|
||||
nhlog::net()->warn(
|
||||
"failed to send verification to_device message: {} {}",
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
});
|
||||
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
|
||||
if constexpr (!std::is_same_v<T,
|
||||
mtx::events::msg::KeyVerificationRequest>) {
|
||||
msg.relations.relations.push_back(this->relation);
|
||||
// Set synthesized to surpress the nheko relation extensions
|
||||
msg.relations.synthesized = true;
|
||||
}
|
||||
(model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type<T>);
|
||||
}
|
||||
|
||||
nhlog::net()->debug(
|
||||
"Sent verification step: {} in state: {}",
|
||||
mtx::events::to_string(mtx::events::to_device_content_to_type<T>),
|
||||
state().toStdString());
|
||||
}
|
||||
nhlog::net()->debug("Sent verification step: {} in state: {}",
|
||||
mtx::events::to_string(mtx::events::to_device_content_to_type<T>),
|
||||
state().toStdString());
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,463 +16,460 @@ using is_detected = typename nheko::detail::detector<nheko::nonesuch, void, Op,
|
|||
|
||||
struct IsStateEvent
|
||||
{
|
||||
template<class T>
|
||||
bool operator()(const mtx::events::StateEvent<T> &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
template<class T>
|
||||
bool operator()(const mtx::events::Event<T> &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
template<class T>
|
||||
bool operator()(const mtx::events::StateEvent<T> &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
template<class T>
|
||||
bool operator()(const mtx::events::Event<T> &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct EventMsgType
|
||||
{
|
||||
template<class E>
|
||||
using msgtype_t = decltype(E::msgtype);
|
||||
template<class T>
|
||||
mtx::events::MessageType operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<msgtype_t, T>::value) {
|
||||
if constexpr (std::is_same_v<std::optional<std::string>,
|
||||
std::remove_cv_t<decltype(e.content.msgtype)>>)
|
||||
return mtx::events::getMessageType(e.content.msgtype.value());
|
||||
else if constexpr (std::is_same_v<
|
||||
std::string,
|
||||
std::remove_cv_t<decltype(e.content.msgtype)>>)
|
||||
return mtx::events::getMessageType(e.content.msgtype);
|
||||
}
|
||||
return mtx::events::MessageType::Unknown;
|
||||
template<class E>
|
||||
using msgtype_t = decltype(E::msgtype);
|
||||
template<class T>
|
||||
mtx::events::MessageType operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<msgtype_t, T>::value) {
|
||||
if constexpr (std::is_same_v<std::optional<std::string>,
|
||||
std::remove_cv_t<decltype(e.content.msgtype)>>)
|
||||
return mtx::events::getMessageType(e.content.msgtype.value());
|
||||
else if constexpr (std::is_same_v<std::string,
|
||||
std::remove_cv_t<decltype(e.content.msgtype)>>)
|
||||
return mtx::events::getMessageType(e.content.msgtype);
|
||||
}
|
||||
return mtx::events::MessageType::Unknown;
|
||||
}
|
||||
};
|
||||
|
||||
struct EventRoomName
|
||||
{
|
||||
template<class T>
|
||||
std::string operator()(const T &e)
|
||||
{
|
||||
if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Name>, T>)
|
||||
return e.content.name;
|
||||
return "";
|
||||
}
|
||||
template<class T>
|
||||
std::string operator()(const T &e)
|
||||
{
|
||||
if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Name>, T>)
|
||||
return e.content.name;
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
struct EventRoomTopic
|
||||
{
|
||||
template<class T>
|
||||
std::string operator()(const T &e)
|
||||
{
|
||||
if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Topic>, T>)
|
||||
return e.content.topic;
|
||||
return "";
|
||||
}
|
||||
template<class T>
|
||||
std::string operator()(const T &e)
|
||||
{
|
||||
if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Topic>, T>)
|
||||
return e.content.topic;
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
struct CallType
|
||||
{
|
||||
template<class T>
|
||||
std::string operator()(const T &e)
|
||||
{
|
||||
if constexpr (std::is_same_v<mtx::events::RoomEvent<mtx::events::msg::CallInvite>,
|
||||
T>) {
|
||||
const char video[] = "m=video";
|
||||
const std::string &sdp = e.content.sdp;
|
||||
return std::search(sdp.cbegin(),
|
||||
sdp.cend(),
|
||||
std::cbegin(video),
|
||||
std::cend(video) - 1,
|
||||
[](unsigned char c1, unsigned char c2) {
|
||||
return std::tolower(c1) == std::tolower(c2);
|
||||
}) != sdp.cend()
|
||||
? "video"
|
||||
: "voice";
|
||||
}
|
||||
return std::string();
|
||||
template<class T>
|
||||
std::string operator()(const T &e)
|
||||
{
|
||||
if constexpr (std::is_same_v<mtx::events::RoomEvent<mtx::events::msg::CallInvite>, T>) {
|
||||
const char video[] = "m=video";
|
||||
const std::string &sdp = e.content.sdp;
|
||||
return std::search(sdp.cbegin(),
|
||||
sdp.cend(),
|
||||
std::cbegin(video),
|
||||
std::cend(video) - 1,
|
||||
[](unsigned char c1, unsigned char c2) {
|
||||
return std::tolower(c1) == std::tolower(c2);
|
||||
}) != sdp.cend()
|
||||
? "video"
|
||||
: "voice";
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
};
|
||||
|
||||
struct EventBody
|
||||
{
|
||||
template<class C>
|
||||
using body_t = decltype(C::body);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<body_t, T>::value) {
|
||||
if constexpr (std::is_same_v<std::optional<std::string>,
|
||||
std::remove_cv_t<decltype(e.content.body)>>)
|
||||
return e.content.body ? e.content.body.value() : "";
|
||||
else if constexpr (std::is_same_v<
|
||||
std::string,
|
||||
std::remove_cv_t<decltype(e.content.body)>>)
|
||||
return e.content.body;
|
||||
}
|
||||
return "";
|
||||
template<class C>
|
||||
using body_t = decltype(C::body);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<body_t, T>::value) {
|
||||
if constexpr (std::is_same_v<std::optional<std::string>,
|
||||
std::remove_cv_t<decltype(e.content.body)>>)
|
||||
return e.content.body ? e.content.body.value() : "";
|
||||
else if constexpr (std::is_same_v<std::string,
|
||||
std::remove_cv_t<decltype(e.content.body)>>)
|
||||
return e.content.body;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
struct EventFormattedBody
|
||||
{
|
||||
template<class C>
|
||||
using formatted_body_t = decltype(C::formatted_body);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::RoomEvent<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<formatted_body_t, T>::value) {
|
||||
if (e.content.format == "org.matrix.custom.html")
|
||||
return e.content.formatted_body;
|
||||
}
|
||||
return "";
|
||||
template<class C>
|
||||
using formatted_body_t = decltype(C::formatted_body);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::RoomEvent<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<formatted_body_t, T>::value) {
|
||||
if (e.content.format == "org.matrix.custom.html")
|
||||
return e.content.formatted_body;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
struct EventFile
|
||||
{
|
||||
template<class Content>
|
||||
using file_t = decltype(Content::file);
|
||||
template<class T>
|
||||
std::optional<mtx::crypto::EncryptedFile> operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<file_t, T>::value)
|
||||
return e.content.file;
|
||||
return std::nullopt;
|
||||
}
|
||||
template<class Content>
|
||||
using file_t = decltype(Content::file);
|
||||
template<class T>
|
||||
std::optional<mtx::crypto::EncryptedFile> operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<file_t, T>::value)
|
||||
return e.content.file;
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct EventUrl
|
||||
{
|
||||
template<class Content>
|
||||
using url_t = decltype(Content::url);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<url_t, T>::value) {
|
||||
if (auto file = EventFile{}(e))
|
||||
return file->url;
|
||||
return e.content.url;
|
||||
}
|
||||
return "";
|
||||
template<class Content>
|
||||
using url_t = decltype(Content::url);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<url_t, T>::value) {
|
||||
if (auto file = EventFile{}(e))
|
||||
return file->url;
|
||||
return e.content.url;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
struct EventThumbnailUrl
|
||||
{
|
||||
template<class Content>
|
||||
using thumbnail_url_t = decltype(Content::info.thumbnail_url);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<thumbnail_url_t, T>::value) {
|
||||
return e.content.info.thumbnail_url;
|
||||
}
|
||||
return "";
|
||||
template<class Content>
|
||||
using thumbnail_url_t = decltype(Content::info.thumbnail_url);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<thumbnail_url_t, T>::value) {
|
||||
return e.content.info.thumbnail_url;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
struct EventBlurhash
|
||||
{
|
||||
template<class Content>
|
||||
using blurhash_t = decltype(Content::info.blurhash);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<blurhash_t, T>::value) {
|
||||
return e.content.info.blurhash;
|
||||
}
|
||||
return "";
|
||||
template<class Content>
|
||||
using blurhash_t = decltype(Content::info.blurhash);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<blurhash_t, T>::value) {
|
||||
return e.content.info.blurhash;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
struct EventFilename
|
||||
{
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Audio> &e)
|
||||
{
|
||||
// body may be the original filename
|
||||
return e.content.body;
|
||||
}
|
||||
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Video> &e)
|
||||
{
|
||||
// body may be the original filename
|
||||
return e.content.body;
|
||||
}
|
||||
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Image> &e)
|
||||
{
|
||||
// body may be the original filename
|
||||
return e.content.body;
|
||||
}
|
||||
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::File> &e)
|
||||
{
|
||||
// body may be the original filename
|
||||
if (!e.content.filename.empty())
|
||||
return e.content.filename;
|
||||
return e.content.body;
|
||||
}
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Audio> &e)
|
||||
{
|
||||
// body may be the original filename
|
||||
return e.content.body;
|
||||
}
|
||||
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Video> &e)
|
||||
{
|
||||
// body may be the original filename
|
||||
return e.content.body;
|
||||
}
|
||||
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Image> &e)
|
||||
{
|
||||
// body may be the original filename
|
||||
return e.content.body;
|
||||
}
|
||||
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::File> &e)
|
||||
{
|
||||
// body may be the original filename
|
||||
if (!e.content.filename.empty())
|
||||
return e.content.filename;
|
||||
return e.content.body;
|
||||
}
|
||||
};
|
||||
|
||||
struct EventMimeType
|
||||
{
|
||||
template<class Content>
|
||||
using mimetype_t = decltype(Content::info.mimetype);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<mimetype_t, T>::value) {
|
||||
return e.content.info.mimetype;
|
||||
}
|
||||
return "";
|
||||
template<class Content>
|
||||
using mimetype_t = decltype(Content::info.mimetype);
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<mimetype_t, T>::value) {
|
||||
return e.content.info.mimetype;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
struct EventFilesize
|
||||
{
|
||||
template<class Content>
|
||||
using filesize_t = decltype(Content::info.size);
|
||||
template<class T>
|
||||
int64_t operator()(const mtx::events::RoomEvent<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<filesize_t, T>::value) {
|
||||
return e.content.info.size;
|
||||
}
|
||||
return 0;
|
||||
template<class Content>
|
||||
using filesize_t = decltype(Content::info.size);
|
||||
template<class T>
|
||||
int64_t operator()(const mtx::events::RoomEvent<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<filesize_t, T>::value) {
|
||||
return e.content.info.size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct EventRelations
|
||||
{
|
||||
template<class Content>
|
||||
using related_ev_id_t = decltype(Content::relations);
|
||||
template<class T>
|
||||
mtx::common::Relations operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<related_ev_id_t, T>::value) {
|
||||
return e.content.relations;
|
||||
}
|
||||
return {};
|
||||
template<class Content>
|
||||
using related_ev_id_t = decltype(Content::relations);
|
||||
template<class T>
|
||||
mtx::common::Relations operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<related_ev_id_t, T>::value) {
|
||||
return e.content.relations;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct SetEventRelations
|
||||
{
|
||||
mtx::common::Relations new_relations;
|
||||
template<class Content>
|
||||
using related_ev_id_t = decltype(Content::relations);
|
||||
template<class T>
|
||||
void operator()(mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<related_ev_id_t, T>::value) {
|
||||
e.content.relations = std::move(new_relations);
|
||||
}
|
||||
mtx::common::Relations new_relations;
|
||||
template<class Content>
|
||||
using related_ev_id_t = decltype(Content::relations);
|
||||
template<class T>
|
||||
void operator()(mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<related_ev_id_t, T>::value) {
|
||||
e.content.relations = std::move(new_relations);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct EventTransactionId
|
||||
{
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::RoomEvent<T> &e)
|
||||
{
|
||||
return e.unsigned_data.transaction_id;
|
||||
}
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
return e.unsigned_data.transaction_id;
|
||||
}
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::RoomEvent<T> &e)
|
||||
{
|
||||
return e.unsigned_data.transaction_id;
|
||||
}
|
||||
template<class T>
|
||||
std::string operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
return e.unsigned_data.transaction_id;
|
||||
}
|
||||
};
|
||||
|
||||
struct EventMediaHeight
|
||||
{
|
||||
template<class Content>
|
||||
using h_t = decltype(Content::info.h);
|
||||
template<class T>
|
||||
uint64_t operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<h_t, T>::value) {
|
||||
return e.content.info.h;
|
||||
}
|
||||
return -1;
|
||||
template<class Content>
|
||||
using h_t = decltype(Content::info.h);
|
||||
template<class T>
|
||||
uint64_t operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<h_t, T>::value) {
|
||||
return e.content.info.h;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
struct EventMediaWidth
|
||||
{
|
||||
template<class Content>
|
||||
using w_t = decltype(Content::info.w);
|
||||
template<class T>
|
||||
uint64_t operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<w_t, T>::value) {
|
||||
return e.content.info.w;
|
||||
}
|
||||
return -1;
|
||||
template<class Content>
|
||||
using w_t = decltype(Content::info.w);
|
||||
template<class T>
|
||||
uint64_t operator()(const mtx::events::Event<T> &e)
|
||||
{
|
||||
if constexpr (is_detected<w_t, T>::value) {
|
||||
return e.content.info.w;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
double
|
||||
eventPropHeight(const mtx::events::RoomEvent<T> &e)
|
||||
{
|
||||
auto w = eventWidth(e);
|
||||
if (w == 0)
|
||||
w = 1;
|
||||
auto w = eventWidth(e);
|
||||
if (w == 0)
|
||||
w = 1;
|
||||
|
||||
double prop = eventHeight(e) / (double)w;
|
||||
double prop = eventHeight(e) / (double)w;
|
||||
|
||||
return prop > 0 ? prop : 1.;
|
||||
return prop > 0 ? prop : 1.;
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
mtx::accessors::event_id(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit([](const auto e) { return e.event_id; }, event);
|
||||
return std::visit([](const auto e) { return e.event_id; }, event);
|
||||
}
|
||||
std::string
|
||||
mtx::accessors::room_id(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit([](const auto e) { return e.room_id; }, event);
|
||||
return std::visit([](const auto e) { return e.room_id; }, event);
|
||||
}
|
||||
|
||||
std::string
|
||||
mtx::accessors::sender(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit([](const auto e) { return e.sender; }, event);
|
||||
return std::visit([](const auto e) { return e.sender; }, event);
|
||||
}
|
||||
|
||||
QDateTime
|
||||
mtx::accessors::origin_server_ts(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return QDateTime::fromMSecsSinceEpoch(
|
||||
std::visit([](const auto e) { return e.origin_server_ts; }, event));
|
||||
return QDateTime::fromMSecsSinceEpoch(
|
||||
std::visit([](const auto e) { return e.origin_server_ts; }, event));
|
||||
}
|
||||
|
||||
std::string
|
||||
mtx::accessors::filename(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventFilename{}, event);
|
||||
return std::visit(EventFilename{}, event);
|
||||
}
|
||||
|
||||
mtx::events::MessageType
|
||||
mtx::accessors::msg_type(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventMsgType{}, event);
|
||||
return std::visit(EventMsgType{}, event);
|
||||
}
|
||||
std::string
|
||||
mtx::accessors::room_name(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventRoomName{}, event);
|
||||
return std::visit(EventRoomName{}, event);
|
||||
}
|
||||
std::string
|
||||
mtx::accessors::room_topic(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventRoomTopic{}, event);
|
||||
return std::visit(EventRoomTopic{}, event);
|
||||
}
|
||||
|
||||
std::string
|
||||
mtx::accessors::call_type(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(CallType{}, event);
|
||||
return std::visit(CallType{}, event);
|
||||
}
|
||||
|
||||
std::string
|
||||
mtx::accessors::body(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventBody{}, event);
|
||||
return std::visit(EventBody{}, event);
|
||||
}
|
||||
|
||||
std::string
|
||||
mtx::accessors::formatted_body(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventFormattedBody{}, event);
|
||||
return std::visit(EventFormattedBody{}, event);
|
||||
}
|
||||
|
||||
QString
|
||||
mtx::accessors::formattedBodyWithFallback(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
auto formatted = formatted_body(event);
|
||||
if (!formatted.empty())
|
||||
return QString::fromStdString(formatted);
|
||||
else
|
||||
return QString::fromStdString(body(event)).toHtmlEscaped().replace("\n", "<br>");
|
||||
auto formatted = formatted_body(event);
|
||||
if (!formatted.empty())
|
||||
return QString::fromStdString(formatted);
|
||||
else
|
||||
return QString::fromStdString(body(event)).toHtmlEscaped().replace("\n", "<br>");
|
||||
}
|
||||
|
||||
std::optional<mtx::crypto::EncryptedFile>
|
||||
mtx::accessors::file(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventFile{}, event);
|
||||
return std::visit(EventFile{}, event);
|
||||
}
|
||||
|
||||
std::string
|
||||
mtx::accessors::url(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventUrl{}, event);
|
||||
return std::visit(EventUrl{}, event);
|
||||
}
|
||||
std::string
|
||||
mtx::accessors::thumbnail_url(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventThumbnailUrl{}, event);
|
||||
return std::visit(EventThumbnailUrl{}, event);
|
||||
}
|
||||
std::string
|
||||
mtx::accessors::blurhash(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventBlurhash{}, event);
|
||||
return std::visit(EventBlurhash{}, event);
|
||||
}
|
||||
std::string
|
||||
mtx::accessors::mimetype(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventMimeType{}, event);
|
||||
return std::visit(EventMimeType{}, event);
|
||||
}
|
||||
mtx::common::Relations
|
||||
mtx::accessors::relations(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventRelations{}, event);
|
||||
return std::visit(EventRelations{}, event);
|
||||
}
|
||||
|
||||
void
|
||||
mtx::accessors::set_relations(mtx::events::collections::TimelineEvents &event,
|
||||
mtx::common::Relations relations)
|
||||
{
|
||||
std::visit(SetEventRelations{std::move(relations)}, event);
|
||||
std::visit(SetEventRelations{std::move(relations)}, event);
|
||||
}
|
||||
|
||||
std::string
|
||||
mtx::accessors::transaction_id(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventTransactionId{}, event);
|
||||
return std::visit(EventTransactionId{}, event);
|
||||
}
|
||||
|
||||
int64_t
|
||||
mtx::accessors::filesize(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventFilesize{}, event);
|
||||
return std::visit(EventFilesize{}, event);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
mtx::accessors::media_height(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventMediaHeight{}, event);
|
||||
return std::visit(EventMediaHeight{}, event);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
mtx::accessors::media_width(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(EventMediaWidth{}, event);
|
||||
return std::visit(EventMediaWidth{}, event);
|
||||
}
|
||||
|
||||
nlohmann::json
|
||||
mtx::accessors::serialize_event(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit([](const auto &e) { return nlohmann::json(e); }, event);
|
||||
return std::visit([](const auto &e) { return nlohmann::json(e); }, event);
|
||||
}
|
||||
|
||||
bool
|
||||
mtx::accessors::is_state_event(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return std::visit(IsStateEvent{}, event);
|
||||
return std::visit(IsStateEvent{}, event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,24 +14,24 @@
|
|||
namespace nheko {
|
||||
struct nonesuch
|
||||
{
|
||||
~nonesuch() = delete;
|
||||
nonesuch(nonesuch const &) = delete;
|
||||
void operator=(nonesuch const &) = delete;
|
||||
~nonesuch() = delete;
|
||||
nonesuch(nonesuch const &) = delete;
|
||||
void operator=(nonesuch const &) = delete;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template<class Default, class AlwaysVoid, template<class...> class Op, class... Args>
|
||||
struct detector
|
||||
{
|
||||
using value_t = std::false_type;
|
||||
using type = Default;
|
||||
using value_t = std::false_type;
|
||||
using type = Default;
|
||||
};
|
||||
|
||||
template<class Default, template<class...> class Op, class... Args>
|
||||
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...>
|
||||
{
|
||||
using value_t = std::true_type;
|
||||
using type = Op<Args...>;
|
||||
using value_t = std::true_type;
|
||||
using type = Op<Args...>;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
|
|
|||
|
|
@ -13,82 +13,81 @@ ImagePackListModel::ImagePackListModel(const std::string &roomId, QObject *paren
|
|||
: QAbstractListModel(parent)
|
||||
, room_id(roomId)
|
||||
{
|
||||
auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt);
|
||||
auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt);
|
||||
|
||||
for (const auto &pack : packs_) {
|
||||
packs.push_back(
|
||||
QSharedPointer<SingleImagePackModel>(new SingleImagePackModel(pack)));
|
||||
}
|
||||
for (const auto &pack : packs_) {
|
||||
packs.push_back(QSharedPointer<SingleImagePackModel>(new SingleImagePackModel(pack)));
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ImagePackListModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return (int)packs.size();
|
||||
return (int)packs.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
ImagePackListModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{Roles::DisplayName, "displayName"},
|
||||
{Roles::AvatarUrl, "avatarUrl"},
|
||||
{Roles::FromAccountData, "fromAccountData"},
|
||||
{Roles::FromCurrentRoom, "fromCurrentRoom"},
|
||||
{Roles::StateKey, "statekey"},
|
||||
{Roles::RoomId, "roomid"},
|
||||
};
|
||||
return {
|
||||
{Roles::DisplayName, "displayName"},
|
||||
{Roles::AvatarUrl, "avatarUrl"},
|
||||
{Roles::FromAccountData, "fromAccountData"},
|
||||
{Roles::FromCurrentRoom, "fromCurrentRoom"},
|
||||
{Roles::StateKey, "statekey"},
|
||||
{Roles::RoomId, "roomid"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
ImagePackListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
const auto &pack = packs.at(index.row());
|
||||
switch (role) {
|
||||
case Roles::DisplayName:
|
||||
return pack->packname();
|
||||
case Roles::AvatarUrl:
|
||||
return pack->avatarUrl();
|
||||
case Roles::FromAccountData:
|
||||
return pack->roomid().isEmpty();
|
||||
case Roles::FromCurrentRoom:
|
||||
return pack->roomid().toStdString() == this->room_id;
|
||||
case Roles::StateKey:
|
||||
return pack->statekey();
|
||||
case Roles::RoomId:
|
||||
return pack->roomid();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
const auto &pack = packs.at(index.row());
|
||||
switch (role) {
|
||||
case Roles::DisplayName:
|
||||
return pack->packname();
|
||||
case Roles::AvatarUrl:
|
||||
return pack->avatarUrl();
|
||||
case Roles::FromAccountData:
|
||||
return pack->roomid().isEmpty();
|
||||
case Roles::FromCurrentRoom:
|
||||
return pack->roomid().toStdString() == this->room_id;
|
||||
case Roles::StateKey:
|
||||
return pack->statekey();
|
||||
case Roles::RoomId:
|
||||
return pack->roomid();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
SingleImagePackModel *
|
||||
ImagePackListModel::packAt(int row)
|
||||
{
|
||||
if (row < 0 || static_cast<size_t>(row) >= packs.size())
|
||||
return {};
|
||||
auto e = packs.at(row).get();
|
||||
QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership);
|
||||
return e;
|
||||
if (row < 0 || static_cast<size_t>(row) >= packs.size())
|
||||
return {};
|
||||
auto e = packs.at(row).get();
|
||||
QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership);
|
||||
return e;
|
||||
}
|
||||
|
||||
SingleImagePackModel *
|
||||
ImagePackListModel::newPack(bool inRoom)
|
||||
{
|
||||
ImagePackInfo info{};
|
||||
if (inRoom)
|
||||
info.source_room = room_id;
|
||||
return new SingleImagePackModel(info);
|
||||
ImagePackInfo info{};
|
||||
if (inRoom)
|
||||
info.source_room = room_id;
|
||||
return new SingleImagePackModel(info);
|
||||
}
|
||||
|
||||
bool
|
||||
ImagePackListModel::containsAccountPack() const
|
||||
{
|
||||
for (const auto &p : packs)
|
||||
if (p->roomid().isEmpty())
|
||||
return true;
|
||||
return false;
|
||||
for (const auto &p : packs)
|
||||
if (p->roomid().isEmpty())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,31 +11,31 @@
|
|||
class SingleImagePackModel;
|
||||
class ImagePackListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool containsAccountPack READ containsAccountPack CONSTANT)
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool containsAccountPack READ containsAccountPack CONSTANT)
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
DisplayName = Qt::UserRole,
|
||||
AvatarUrl,
|
||||
FromAccountData,
|
||||
FromCurrentRoom,
|
||||
StateKey,
|
||||
RoomId,
|
||||
};
|
||||
enum Roles
|
||||
{
|
||||
DisplayName = Qt::UserRole,
|
||||
AvatarUrl,
|
||||
FromAccountData,
|
||||
FromCurrentRoom,
|
||||
StateKey,
|
||||
RoomId,
|
||||
};
|
||||
|
||||
ImagePackListModel(const std::string &roomId, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
ImagePackListModel(const std::string &roomId, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
Q_INVOKABLE SingleImagePackModel *packAt(int row);
|
||||
Q_INVOKABLE SingleImagePackModel *newPack(bool inRoom);
|
||||
Q_INVOKABLE SingleImagePackModel *packAt(int row);
|
||||
Q_INVOKABLE SingleImagePackModel *newPack(bool inRoom);
|
||||
|
||||
bool containsAccountPack() const;
|
||||
bool containsAccountPack() const;
|
||||
|
||||
private:
|
||||
std::string room_id;
|
||||
std::string room_id;
|
||||
|
||||
std::vector<QSharedPointer<SingleImagePackModel>> packs;
|
||||
std::vector<QSharedPointer<SingleImagePackModel>> packs;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,69 +16,68 @@ InviteesModel::InviteesModel(QObject *parent)
|
|||
void
|
||||
InviteesModel::addUser(QString mxid)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count());
|
||||
beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count());
|
||||
|
||||
auto invitee = new Invitee{mxid, this};
|
||||
auto indexOfInvitee = invitees_.count();
|
||||
connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() {
|
||||
emit dataChanged(index(indexOfInvitee), index(indexOfInvitee));
|
||||
});
|
||||
auto invitee = new Invitee{mxid, this};
|
||||
auto indexOfInvitee = invitees_.count();
|
||||
connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() {
|
||||
emit dataChanged(index(indexOfInvitee), index(indexOfInvitee));
|
||||
});
|
||||
|
||||
invitees_.push_back(invitee);
|
||||
invitees_.push_back(invitee);
|
||||
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
InviteesModel::roleNames() const
|
||||
{
|
||||
return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}};
|
||||
return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}};
|
||||
}
|
||||
|
||||
QVariant
|
||||
InviteesModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0)
|
||||
return {};
|
||||
if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0)
|
||||
return {};
|
||||
|
||||
switch (role) {
|
||||
case Mxid:
|
||||
return invitees_[index.row()]->mxid_;
|
||||
case DisplayName:
|
||||
return invitees_[index.row()]->displayName_;
|
||||
case AvatarUrl:
|
||||
return invitees_[index.row()]->avatarUrl_;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
switch (role) {
|
||||
case Mxid:
|
||||
return invitees_[index.row()]->mxid_;
|
||||
case DisplayName:
|
||||
return invitees_[index.row()]->displayName_;
|
||||
case AvatarUrl:
|
||||
return invitees_[index.row()]->avatarUrl_;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QStringList
|
||||
InviteesModel::mxids()
|
||||
{
|
||||
QStringList mxidList;
|
||||
for (int i = 0; i < invitees_.length(); ++i)
|
||||
mxidList.push_back(invitees_[i]->mxid_);
|
||||
return mxidList;
|
||||
QStringList mxidList;
|
||||
for (int i = 0; i < invitees_.length(); ++i)
|
||||
mxidList.push_back(invitees_[i]->mxid_);
|
||||
return mxidList;
|
||||
}
|
||||
|
||||
Invitee::Invitee(const QString &mxid, QObject *parent)
|
||||
: QObject{parent}
|
||||
, mxid_{mxid}
|
||||
{
|
||||
http::client()->get_profile(
|
||||
mxid_.toStdString(),
|
||||
[this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to retrieve profile info");
|
||||
emit userInfoLoaded();
|
||||
return;
|
||||
}
|
||||
http::client()->get_profile(
|
||||
mxid_.toStdString(), [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to retrieve profile info");
|
||||
emit userInfoLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
displayName_ = QString::fromStdString(res.display_name);
|
||||
avatarUrl_ = QString::fromStdString(res.avatar_url);
|
||||
displayName_ = QString::fromStdString(res.display_name);
|
||||
avatarUrl_ = QString::fromStdString(res.avatar_url);
|
||||
|
||||
emit userInfoLoaded();
|
||||
});
|
||||
emit userInfoLoaded();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,54 +10,54 @@
|
|||
|
||||
class Invitee : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Invitee(const QString &mxid, QObject *parent = nullptr);
|
||||
Invitee(const QString &mxid, QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void userInfoLoaded();
|
||||
void userInfoLoaded();
|
||||
|
||||
private:
|
||||
const QString mxid_;
|
||||
QString displayName_;
|
||||
QString avatarUrl_;
|
||||
const QString mxid_;
|
||||
QString displayName_;
|
||||
QString avatarUrl_;
|
||||
|
||||
friend class InviteesModel;
|
||||
friend class InviteesModel;
|
||||
};
|
||||
|
||||
class InviteesModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
Mxid,
|
||||
DisplayName,
|
||||
AvatarUrl,
|
||||
};
|
||||
enum Roles
|
||||
{
|
||||
Mxid,
|
||||
DisplayName,
|
||||
AvatarUrl,
|
||||
};
|
||||
|
||||
InviteesModel(QObject *parent = nullptr);
|
||||
InviteesModel(QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void addUser(QString mxid);
|
||||
Q_INVOKABLE void addUser(QString mxid);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex & = QModelIndex()) const override
|
||||
{
|
||||
return (int)invitees_.size();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QStringList mxids();
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex & = QModelIndex()) const override
|
||||
{
|
||||
return (int)invitees_.size();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QStringList mxids();
|
||||
|
||||
signals:
|
||||
void accept();
|
||||
void countChanged();
|
||||
void accept();
|
||||
void countChanged();
|
||||
|
||||
private:
|
||||
QVector<Invitee *> invitees_;
|
||||
QVector<Invitee *> invitees_;
|
||||
};
|
||||
|
||||
#endif // INVITEESMODEL_H
|
||||
|
|
|
|||
|
|
@ -22,20 +22,20 @@
|
|||
static QPixmap
|
||||
clipRadius(QPixmap img, double radius)
|
||||
{
|
||||
QPixmap out(img.size());
|
||||
out.fill(Qt::transparent);
|
||||
QPixmap out(img.size());
|
||||
out.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&out);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
QPainter painter(&out);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
|
||||
QPainterPath ppath;
|
||||
ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize);
|
||||
QPainterPath ppath;
|
||||
ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize);
|
||||
|
||||
painter.setClipPath(ppath);
|
||||
painter.drawPixmap(img.rect(), img);
|
||||
painter.setClipPath(ppath);
|
||||
painter.drawPixmap(img.rect(), img);
|
||||
|
||||
return out;
|
||||
return out;
|
||||
}
|
||||
|
||||
JdenticonResponse::JdenticonResponse(const QString &key,
|
||||
|
|
@ -49,64 +49,64 @@ JdenticonResponse::JdenticonResponse(const QString &key,
|
|||
, m_pixmap{m_requestedSize}
|
||||
, jdenticonInterface_{Jdenticon::getJdenticonInterface()}
|
||||
{
|
||||
setAutoDelete(false);
|
||||
setAutoDelete(false);
|
||||
}
|
||||
|
||||
void
|
||||
JdenticonResponse::run()
|
||||
{
|
||||
m_pixmap.fill(Qt::transparent);
|
||||
m_pixmap.fill(Qt::transparent);
|
||||
|
||||
QPainter painter;
|
||||
painter.begin(&m_pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
QPainter painter;
|
||||
painter.begin(&m_pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
|
||||
try {
|
||||
QSvgRenderer renderer{
|
||||
jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()};
|
||||
renderer.render(&painter);
|
||||
} catch (std::exception &e) {
|
||||
nhlog::ui()->error(
|
||||
"caught {} in jdenticonprovider, key '{}'", e.what(), m_key.toStdString());
|
||||
}
|
||||
try {
|
||||
QSvgRenderer renderer{
|
||||
jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()};
|
||||
renderer.render(&painter);
|
||||
} catch (std::exception &e) {
|
||||
nhlog::ui()->error(
|
||||
"caught {} in jdenticonprovider, key '{}'", e.what(), m_key.toStdString());
|
||||
}
|
||||
|
||||
painter.end();
|
||||
painter.end();
|
||||
|
||||
m_pixmap = clipRadius(m_pixmap, m_radius);
|
||||
m_pixmap = clipRadius(m_pixmap, m_radius);
|
||||
|
||||
emit finished();
|
||||
emit finished();
|
||||
}
|
||||
|
||||
namespace Jdenticon {
|
||||
JdenticonInterface *
|
||||
getJdenticonInterface()
|
||||
{
|
||||
static JdenticonInterface *interface = nullptr;
|
||||
static bool interfaceExists{true};
|
||||
static JdenticonInterface *interface = nullptr;
|
||||
static bool interfaceExists{true};
|
||||
|
||||
if (interface == nullptr && interfaceExists) {
|
||||
QDir pluginsDir(qApp->applicationDirPath());
|
||||
if (interface == nullptr && interfaceExists) {
|
||||
QDir pluginsDir(qApp->applicationDirPath());
|
||||
|
||||
bool plugins = pluginsDir.cd("plugins");
|
||||
if (plugins) {
|
||||
for (const QString &fileName : pluginsDir.entryList(QDir::Files)) {
|
||||
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
|
||||
QObject *plugin = pluginLoader.instance();
|
||||
if (plugin) {
|
||||
interface = qobject_cast<JdenticonInterface *>(plugin);
|
||||
if (interface) {
|
||||
nhlog::ui()->info("Loaded jdenticon plugin.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nhlog::ui()->info("jdenticon plugin not found.");
|
||||
interfaceExists = false;
|
||||
bool plugins = pluginsDir.cd("plugins");
|
||||
if (plugins) {
|
||||
for (const QString &fileName : pluginsDir.entryList(QDir::Files)) {
|
||||
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
|
||||
QObject *plugin = pluginLoader.instance();
|
||||
if (plugin) {
|
||||
interface = qobject_cast<JdenticonInterface *>(plugin);
|
||||
if (interface) {
|
||||
nhlog::ui()->info("Loaded jdenticon plugin.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nhlog::ui()->info("jdenticon plugin not found.");
|
||||
interfaceExists = false;
|
||||
}
|
||||
}
|
||||
|
||||
return interface;
|
||||
return interface;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,59 +23,58 @@ class JdenticonResponse
|
|||
, public QRunnable
|
||||
{
|
||||
public:
|
||||
JdenticonResponse(const QString &key, bool crop, double radius, const QSize &requestedSize);
|
||||
JdenticonResponse(const QString &key, bool crop, double radius, const QSize &requestedSize);
|
||||
|
||||
QQuickTextureFactory *textureFactory() const override
|
||||
{
|
||||
return QQuickTextureFactory::textureFactoryForImage(m_pixmap.toImage());
|
||||
}
|
||||
QQuickTextureFactory *textureFactory() const override
|
||||
{
|
||||
return QQuickTextureFactory::textureFactoryForImage(m_pixmap.toImage());
|
||||
}
|
||||
|
||||
void run() override;
|
||||
void run() override;
|
||||
|
||||
QString m_key;
|
||||
bool m_crop;
|
||||
double m_radius;
|
||||
QSize m_requestedSize;
|
||||
QPixmap m_pixmap;
|
||||
JdenticonInterface *jdenticonInterface_ = nullptr;
|
||||
QString m_key;
|
||||
bool m_crop;
|
||||
double m_radius;
|
||||
QSize m_requestedSize;
|
||||
QPixmap m_pixmap;
|
||||
JdenticonInterface *jdenticonInterface_ = nullptr;
|
||||
};
|
||||
|
||||
class JdenticonProvider
|
||||
: public QObject
|
||||
, public QQuickAsyncImageProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; }
|
||||
static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; }
|
||||
|
||||
public slots:
|
||||
QQuickImageResponse *requestImageResponse(const QString &id,
|
||||
const QSize &requestedSize) override
|
||||
{
|
||||
auto id_ = id;
|
||||
bool crop = true;
|
||||
double radius = 0;
|
||||
QQuickImageResponse *requestImageResponse(const QString &id,
|
||||
const QSize &requestedSize) override
|
||||
{
|
||||
auto id_ = id;
|
||||
bool crop = true;
|
||||
double radius = 0;
|
||||
|
||||
auto queryStart = id.lastIndexOf('?');
|
||||
if (queryStart != -1) {
|
||||
id_ = id.left(queryStart);
|
||||
auto query = id.midRef(queryStart + 1);
|
||||
auto queryBits = query.split('&');
|
||||
auto queryStart = id.lastIndexOf('?');
|
||||
if (queryStart != -1) {
|
||||
id_ = id.left(queryStart);
|
||||
auto query = id.midRef(queryStart + 1);
|
||||
auto queryBits = query.split('&');
|
||||
|
||||
for (auto b : queryBits) {
|
||||
if (b.startsWith("radius=")) {
|
||||
radius = b.mid(7).toDouble();
|
||||
}
|
||||
}
|
||||
for (auto b : queryBits) {
|
||||
if (b.startsWith("radius=")) {
|
||||
radius = b.mid(7).toDouble();
|
||||
}
|
||||
|
||||
JdenticonResponse *response =
|
||||
new JdenticonResponse(id_, crop, radius, requestedSize);
|
||||
pool.start(response);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
JdenticonResponse *response = new JdenticonResponse(id_, crop, radius, requestedSize);
|
||||
pool.start(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private:
|
||||
QThreadPool pool;
|
||||
QThreadPool pool;
|
||||
};
|
||||
|
|
|
|||
103
src/Logging.cpp
103
src/Logging.cpp
|
|
@ -25,35 +25,35 @@ constexpr auto MAX_LOG_FILES = 3;
|
|||
void
|
||||
qmlMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
std::string localMsg = msg.toStdString();
|
||||
const char *file = context.file ? context.file : "";
|
||||
const char *function = context.function ? context.function : "";
|
||||
std::string localMsg = msg.toStdString();
|
||||
const char *file = context.file ? context.file : "";
|
||||
const char *function = context.function ? context.function : "";
|
||||
|
||||
if (
|
||||
// The default style has the point size set. If you use pixel size anywhere, you get
|
||||
// that warning, which is useless, since sometimes you need the pixel size to match the
|
||||
// text to the size of the outer element for example. This is done in the avatar and
|
||||
// without that you get one warning for every Avatar displayed, which is stupid!
|
||||
msg.endsWith(QStringLiteral("Both point size and pixel size set. Using pixel size.")))
|
||||
return;
|
||||
if (
|
||||
// The default style has the point size set. If you use pixel size anywhere, you get
|
||||
// that warning, which is useless, since sometimes you need the pixel size to match the
|
||||
// text to the size of the outer element for example. This is done in the avatar and
|
||||
// without that you get one warning for every Avatar displayed, which is stupid!
|
||||
msg.endsWith(QStringLiteral("Both point size and pixel size set. Using pixel size.")))
|
||||
return;
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
nhlog::qml()->debug("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
nhlog::qml()->info("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
nhlog::qml()->warn("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
}
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
nhlog::qml()->debug("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
nhlog::qml()->info("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
nhlog::qml()->warn("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,60 +63,59 @@ bool enable_debug_log_from_commandline = false;
|
|||
void
|
||||
init(const std::string &file_path)
|
||||
{
|
||||
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
||||
file_path, MAX_FILE_SIZE, MAX_LOG_FILES);
|
||||
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
||||
file_path, MAX_FILE_SIZE, MAX_LOG_FILES);
|
||||
|
||||
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||
|
||||
std::vector<spdlog::sink_ptr> sinks;
|
||||
sinks.push_back(file_sink);
|
||||
sinks.push_back(console_sink);
|
||||
std::vector<spdlog::sink_ptr> sinks;
|
||||
sinks.push_back(file_sink);
|
||||
sinks.push_back(console_sink);
|
||||
|
||||
net_logger = std::make_shared<spdlog::logger>("net", std::begin(sinks), std::end(sinks));
|
||||
ui_logger = std::make_shared<spdlog::logger>("ui", std::begin(sinks), std::end(sinks));
|
||||
db_logger = std::make_shared<spdlog::logger>("db", std::begin(sinks), std::end(sinks));
|
||||
crypto_logger =
|
||||
std::make_shared<spdlog::logger>("crypto", std::begin(sinks), std::end(sinks));
|
||||
qml_logger = std::make_shared<spdlog::logger>("qml", std::begin(sinks), std::end(sinks));
|
||||
net_logger = std::make_shared<spdlog::logger>("net", std::begin(sinks), std::end(sinks));
|
||||
ui_logger = std::make_shared<spdlog::logger>("ui", std::begin(sinks), std::end(sinks));
|
||||
db_logger = std::make_shared<spdlog::logger>("db", std::begin(sinks), std::end(sinks));
|
||||
crypto_logger = std::make_shared<spdlog::logger>("crypto", std::begin(sinks), std::end(sinks));
|
||||
qml_logger = std::make_shared<spdlog::logger>("qml", std::begin(sinks), std::end(sinks));
|
||||
|
||||
if (nheko::enable_debug_log || enable_debug_log_from_commandline) {
|
||||
db_logger->set_level(spdlog::level::trace);
|
||||
ui_logger->set_level(spdlog::level::trace);
|
||||
crypto_logger->set_level(spdlog::level::trace);
|
||||
net_logger->set_level(spdlog::level::trace);
|
||||
qml_logger->set_level(spdlog::level::trace);
|
||||
}
|
||||
if (nheko::enable_debug_log || enable_debug_log_from_commandline) {
|
||||
db_logger->set_level(spdlog::level::trace);
|
||||
ui_logger->set_level(spdlog::level::trace);
|
||||
crypto_logger->set_level(spdlog::level::trace);
|
||||
net_logger->set_level(spdlog::level::trace);
|
||||
qml_logger->set_level(spdlog::level::trace);
|
||||
}
|
||||
|
||||
qInstallMessageHandler(qmlMessageHandler);
|
||||
qInstallMessageHandler(qmlMessageHandler);
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
ui()
|
||||
{
|
||||
return ui_logger;
|
||||
return ui_logger;
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
net()
|
||||
{
|
||||
return net_logger;
|
||||
return net_logger;
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
db()
|
||||
{
|
||||
return db_logger;
|
||||
return db_logger;
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
crypto()
|
||||
{
|
||||
return crypto_logger;
|
||||
return crypto_logger;
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
qml()
|
||||
{
|
||||
return qml_logger;
|
||||
return qml_logger;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,477 +34,468 @@ LoginPage::LoginPage(QWidget *parent)
|
|||
: QWidget(parent)
|
||||
, inferredServerAddress_()
|
||||
{
|
||||
qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
|
||||
qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
|
||||
|
||||
top_layout_ = new QVBoxLayout();
|
||||
top_layout_ = new QVBoxLayout();
|
||||
|
||||
top_bar_layout_ = new QHBoxLayout();
|
||||
top_bar_layout_->setSpacing(0);
|
||||
top_bar_layout_->setMargin(0);
|
||||
top_bar_layout_ = new QHBoxLayout();
|
||||
top_bar_layout_->setSpacing(0);
|
||||
top_bar_layout_->setMargin(0);
|
||||
|
||||
back_button_ = new FlatButton(this);
|
||||
back_button_->setMinimumSize(QSize(30, 30));
|
||||
back_button_ = new FlatButton(this);
|
||||
back_button_->setMinimumSize(QSize(30, 30));
|
||||
|
||||
top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
top_bar_layout_->addStretch(1);
|
||||
top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
top_bar_layout_->addStretch(1);
|
||||
|
||||
QIcon icon;
|
||||
icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png");
|
||||
QIcon icon;
|
||||
icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png");
|
||||
|
||||
back_button_->setIcon(icon);
|
||||
back_button_->setIconSize(QSize(32, 32));
|
||||
back_button_->setIcon(icon);
|
||||
back_button_->setIconSize(QSize(32, 32));
|
||||
|
||||
QIcon logo;
|
||||
logo.addFile(":/logos/login.png");
|
||||
QIcon logo;
|
||||
logo.addFile(":/logos/login.png");
|
||||
|
||||
logo_ = new QLabel(this);
|
||||
logo_->setPixmap(logo.pixmap(128));
|
||||
logo_ = new QLabel(this);
|
||||
logo_->setPixmap(logo.pixmap(128));
|
||||
|
||||
logo_layout_ = new QHBoxLayout();
|
||||
logo_layout_->setContentsMargins(0, 0, 0, 20);
|
||||
logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
|
||||
logo_layout_ = new QHBoxLayout();
|
||||
logo_layout_->setContentsMargins(0, 0, 0, 20);
|
||||
logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
|
||||
|
||||
form_wrapper_ = new QHBoxLayout();
|
||||
form_widget_ = new QWidget();
|
||||
form_widget_->setMinimumSize(QSize(350, 200));
|
||||
form_wrapper_ = new QHBoxLayout();
|
||||
form_widget_ = new QWidget();
|
||||
form_widget_->setMinimumSize(QSize(350, 200));
|
||||
|
||||
form_layout_ = new QVBoxLayout();
|
||||
form_layout_->setSpacing(20);
|
||||
form_layout_->setContentsMargins(0, 0, 0, 30);
|
||||
form_widget_->setLayout(form_layout_);
|
||||
form_layout_ = new QVBoxLayout();
|
||||
form_layout_->setSpacing(20);
|
||||
form_layout_->setContentsMargins(0, 0, 0, 30);
|
||||
form_widget_->setLayout(form_layout_);
|
||||
|
||||
form_wrapper_->addStretch(1);
|
||||
form_wrapper_->addWidget(form_widget_);
|
||||
form_wrapper_->addStretch(1);
|
||||
form_wrapper_->addStretch(1);
|
||||
form_wrapper_->addWidget(form_widget_);
|
||||
form_wrapper_->addStretch(1);
|
||||
|
||||
matrixid_input_ = new TextField(this);
|
||||
matrixid_input_->setLabel(tr("Matrix ID"));
|
||||
matrixid_input_->setRegexp(QRegularExpression("@.+?:.{3,}"));
|
||||
matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org"));
|
||||
matrixid_input_->setToolTip(
|
||||
tr("Your login name. A mxid should start with @ followed by the user id. After the user "
|
||||
"id you need to include your server name after a :.\nYou can also put your homeserver "
|
||||
"address there, if your server doesn't support .well-known lookup.\nExample: "
|
||||
"@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a "
|
||||
"field to enter the server manually."));
|
||||
matrixid_input_ = new TextField(this);
|
||||
matrixid_input_->setLabel(tr("Matrix ID"));
|
||||
matrixid_input_->setRegexp(QRegularExpression("@.+?:.{3,}"));
|
||||
matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org"));
|
||||
matrixid_input_->setToolTip(
|
||||
tr("Your login name. A mxid should start with @ followed by the user id. After the user "
|
||||
"id you need to include your server name after a :.\nYou can also put your homeserver "
|
||||
"address there, if your server doesn't support .well-known lookup.\nExample: "
|
||||
"@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a "
|
||||
"field to enter the server manually."));
|
||||
|
||||
spinner_ = new LoadingIndicator(this);
|
||||
spinner_->setFixedHeight(40);
|
||||
spinner_->setFixedWidth(40);
|
||||
spinner_->hide();
|
||||
spinner_ = new LoadingIndicator(this);
|
||||
spinner_->setFixedHeight(40);
|
||||
spinner_->setFixedWidth(40);
|
||||
spinner_->hide();
|
||||
|
||||
errorIcon_ = new QLabel(this);
|
||||
errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png"));
|
||||
errorIcon_->hide();
|
||||
errorIcon_ = new QLabel(this);
|
||||
errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png"));
|
||||
errorIcon_->hide();
|
||||
|
||||
matrixidLayout_ = new QHBoxLayout();
|
||||
matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter);
|
||||
matrixidLayout_ = new QHBoxLayout();
|
||||
matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter);
|
||||
|
||||
QFont font;
|
||||
QFont font;
|
||||
|
||||
error_matrixid_label_ = new QLabel(this);
|
||||
error_matrixid_label_->setFont(font);
|
||||
error_matrixid_label_->setWordWrap(true);
|
||||
error_matrixid_label_ = new QLabel(this);
|
||||
error_matrixid_label_->setFont(font);
|
||||
error_matrixid_label_->setWordWrap(true);
|
||||
|
||||
password_input_ = new TextField(this);
|
||||
password_input_->setLabel(tr("Password"));
|
||||
password_input_->setEchoMode(QLineEdit::Password);
|
||||
password_input_->setToolTip(tr("Your password."));
|
||||
password_input_ = new TextField(this);
|
||||
password_input_->setLabel(tr("Password"));
|
||||
password_input_->setEchoMode(QLineEdit::Password);
|
||||
password_input_->setToolTip(tr("Your password."));
|
||||
|
||||
deviceName_ = new TextField(this);
|
||||
deviceName_->setLabel(tr("Device name"));
|
||||
deviceName_->setToolTip(
|
||||
tr("A name for this device, which will be shown to others, when verifying your devices. "
|
||||
"If none is provided a default is used."));
|
||||
deviceName_ = new TextField(this);
|
||||
deviceName_->setLabel(tr("Device name"));
|
||||
deviceName_->setToolTip(
|
||||
tr("A name for this device, which will be shown to others, when verifying your devices. "
|
||||
"If none is provided a default is used."));
|
||||
|
||||
serverInput_ = new TextField(this);
|
||||
serverInput_->setLabel(tr("Homeserver address"));
|
||||
serverInput_->setPlaceholderText(tr("server.my:8787"));
|
||||
serverInput_->setToolTip(tr("The address that can be used to contact you homeservers "
|
||||
"client API.\nExample: https://server.my:8787"));
|
||||
serverInput_->hide();
|
||||
serverInput_ = new TextField(this);
|
||||
serverInput_->setLabel(tr("Homeserver address"));
|
||||
serverInput_->setPlaceholderText(tr("server.my:8787"));
|
||||
serverInput_->setToolTip(tr("The address that can be used to contact you homeservers "
|
||||
"client API.\nExample: https://server.my:8787"));
|
||||
serverInput_->hide();
|
||||
|
||||
serverLayout_ = new QHBoxLayout();
|
||||
serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter);
|
||||
serverLayout_ = new QHBoxLayout();
|
||||
serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter);
|
||||
|
||||
form_layout_->addLayout(matrixidLayout_);
|
||||
form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(password_input_);
|
||||
form_layout_->addWidget(deviceName_, Qt::AlignHCenter);
|
||||
form_layout_->addLayout(serverLayout_);
|
||||
form_layout_->addLayout(matrixidLayout_);
|
||||
form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(password_input_);
|
||||
form_layout_->addWidget(deviceName_, Qt::AlignHCenter);
|
||||
form_layout_->addLayout(serverLayout_);
|
||||
|
||||
error_matrixid_label_->hide();
|
||||
error_matrixid_label_->hide();
|
||||
|
||||
button_layout_ = new QHBoxLayout();
|
||||
button_layout_->setSpacing(20);
|
||||
button_layout_->setContentsMargins(0, 0, 0, 30);
|
||||
button_layout_ = new QHBoxLayout();
|
||||
button_layout_->setSpacing(20);
|
||||
button_layout_->setContentsMargins(0, 0, 0, 30);
|
||||
|
||||
login_button_ = new RaisedButton(tr("LOGIN"), this);
|
||||
login_button_->setMinimumSize(150, 65);
|
||||
login_button_->setFontSize(20);
|
||||
login_button_->setCornerRadius(3);
|
||||
login_button_ = new RaisedButton(tr("LOGIN"), this);
|
||||
login_button_->setMinimumSize(150, 65);
|
||||
login_button_->setFontSize(20);
|
||||
login_button_->setCornerRadius(3);
|
||||
|
||||
sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this);
|
||||
sso_login_button_->setMinimumSize(150, 65);
|
||||
sso_login_button_->setFontSize(20);
|
||||
sso_login_button_->setCornerRadius(3);
|
||||
sso_login_button_->setVisible(false);
|
||||
sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this);
|
||||
sso_login_button_->setMinimumSize(150, 65);
|
||||
sso_login_button_->setFontSize(20);
|
||||
sso_login_button_->setCornerRadius(3);
|
||||
sso_login_button_->setVisible(false);
|
||||
|
||||
button_layout_->addStretch(1);
|
||||
button_layout_->addWidget(login_button_);
|
||||
button_layout_->addWidget(sso_login_button_);
|
||||
button_layout_->addStretch(1);
|
||||
button_layout_->addStretch(1);
|
||||
button_layout_->addWidget(login_button_);
|
||||
button_layout_->addWidget(sso_login_button_);
|
||||
button_layout_->addStretch(1);
|
||||
|
||||
error_label_ = new QLabel(this);
|
||||
error_label_->setFont(font);
|
||||
error_label_->setWordWrap(true);
|
||||
error_label_ = new QLabel(this);
|
||||
error_label_->setFont(font);
|
||||
error_label_->setWordWrap(true);
|
||||
|
||||
top_layout_->addLayout(top_bar_layout_);
|
||||
top_layout_->addStretch(1);
|
||||
top_layout_->addLayout(logo_layout_);
|
||||
top_layout_->addLayout(form_wrapper_);
|
||||
top_layout_->addStretch(1);
|
||||
top_layout_->addLayout(button_layout_);
|
||||
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
|
||||
top_layout_->addStretch(1);
|
||||
top_layout_->addLayout(top_bar_layout_);
|
||||
top_layout_->addStretch(1);
|
||||
top_layout_->addLayout(logo_layout_);
|
||||
top_layout_->addLayout(form_wrapper_);
|
||||
top_layout_->addStretch(1);
|
||||
top_layout_->addLayout(button_layout_);
|
||||
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
|
||||
top_layout_->addStretch(1);
|
||||
|
||||
setLayout(top_layout_);
|
||||
setLayout(top_layout_);
|
||||
|
||||
connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection);
|
||||
connect(
|
||||
this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection);
|
||||
connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection);
|
||||
connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection);
|
||||
|
||||
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
|
||||
connect(login_button_, &RaisedButton::clicked, this, [this]() {
|
||||
onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO);
|
||||
});
|
||||
connect(sso_login_button_, &RaisedButton::clicked, this, [this]() {
|
||||
onLoginButtonClicked(LoginMethod::SSO);
|
||||
});
|
||||
connect(this,
|
||||
&LoginPage::showErrorMessage,
|
||||
this,
|
||||
static_cast<void (LoginPage::*)(QLabel *, const QString &)>(&LoginPage::showError),
|
||||
Qt::QueuedConnection);
|
||||
connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
|
||||
connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
|
||||
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
|
||||
connect(login_button_, &RaisedButton::clicked, this, [this]() {
|
||||
onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO);
|
||||
});
|
||||
connect(sso_login_button_, &RaisedButton::clicked, this, [this]() {
|
||||
onLoginButtonClicked(LoginMethod::SSO);
|
||||
});
|
||||
connect(this,
|
||||
&LoginPage::showErrorMessage,
|
||||
this,
|
||||
static_cast<void (LoginPage::*)(QLabel *, const QString &)>(&LoginPage::showError),
|
||||
Qt::QueuedConnection);
|
||||
connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
|
||||
connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
|
||||
}
|
||||
void
|
||||
LoginPage::showError(const QString &msg)
|
||||
{
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
error_label_->setFixedHeight((int)qCeil(width / 200.0) * height);
|
||||
error_label_->setText(msg);
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
error_label_->setFixedHeight((int)qCeil(width / 200.0) * height);
|
||||
error_label_->setText(msg);
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::showError(QLabel *label, const QString &msg)
|
||||
{
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
label->setFixedHeight((int)qCeil(width / 200.0) * height);
|
||||
label->setText(msg);
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
label->setFixedHeight((int)qCeil(width / 200.0) * height);
|
||||
label->setText(msg);
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::onMatrixIdEntered()
|
||||
{
|
||||
error_label_->setText("");
|
||||
error_label_->setText("");
|
||||
|
||||
User user;
|
||||
User user;
|
||||
|
||||
if (!matrixid_input_->isValid()) {
|
||||
error_matrixid_label_->show();
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
return;
|
||||
if (!matrixid_input_->isValid()) {
|
||||
error_matrixid_label_->show();
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
return;
|
||||
} else {
|
||||
error_matrixid_label_->setText("");
|
||||
error_matrixid_label_->hide();
|
||||
}
|
||||
|
||||
try {
|
||||
user = parse<User>(matrixid_input_->text().toStdString());
|
||||
} catch (const std::exception &) {
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
return;
|
||||
}
|
||||
|
||||
QString homeServer = QString::fromStdString(user.hostname());
|
||||
if (homeServer != inferredServerAddress_) {
|
||||
serverInput_->hide();
|
||||
serverLayout_->removeWidget(errorIcon_);
|
||||
errorIcon_->hide();
|
||||
if (serverInput_->isVisible()) {
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
spinner_->start();
|
||||
} else {
|
||||
error_matrixid_label_->setText("");
|
||||
error_matrixid_label_->hide();
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
spinner_->start();
|
||||
}
|
||||
|
||||
try {
|
||||
user = parse<User>(matrixid_input_->text().toStdString());
|
||||
} catch (const std::exception &) {
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
return;
|
||||
}
|
||||
inferredServerAddress_ = homeServer;
|
||||
serverInput_->setText(homeServer);
|
||||
|
||||
QString homeServer = QString::fromStdString(user.hostname());
|
||||
if (homeServer != inferredServerAddress_) {
|
||||
serverInput_->hide();
|
||||
serverLayout_->removeWidget(errorIcon_);
|
||||
errorIcon_->hide();
|
||||
if (serverInput_->isVisible()) {
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
spinner_->start();
|
||||
} else {
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
spinner_->start();
|
||||
}
|
||||
http::client()->set_server(user.hostname());
|
||||
http::client()->verify_certificates(
|
||||
!UserSettings::instance()->disableCertificateValidation());
|
||||
|
||||
inferredServerAddress_ = homeServer;
|
||||
serverInput_->setText(homeServer);
|
||||
http::client()->well_known(
|
||||
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
if (err->status_code == 404) {
|
||||
nhlog::net()->info("Autodiscovery: No .well-known.");
|
||||
checkHomeserverVersion();
|
||||
return;
|
||||
}
|
||||
|
||||
http::client()->set_server(user.hostname());
|
||||
http::client()->verify_certificates(
|
||||
!UserSettings::instance()->disableCertificateValidation());
|
||||
if (!err->parse_error.empty()) {
|
||||
emit versionErrorCb(tr("Autodiscovery failed. Received malformed response."));
|
||||
nhlog::net()->error("Autodiscovery failed. Received malformed response.");
|
||||
return;
|
||||
}
|
||||
|
||||
http::client()->well_known([this](const mtx::responses::WellKnown &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
if (err->status_code == 404) {
|
||||
nhlog::net()->info("Autodiscovery: No .well-known.");
|
||||
checkHomeserverVersion();
|
||||
return;
|
||||
}
|
||||
emit versionErrorCb(tr("Autodiscovery failed. Unknown error when "
|
||||
"requesting .well-known."));
|
||||
nhlog::net()->error("Autodiscovery failed. Unknown error when "
|
||||
"requesting .well-known. {} {}",
|
||||
err->status_code,
|
||||
err->error_code);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!err->parse_error.empty()) {
|
||||
emit versionErrorCb(
|
||||
tr("Autodiscovery failed. Received malformed response."));
|
||||
nhlog::net()->error(
|
||||
"Autodiscovery failed. Received malformed response.");
|
||||
return;
|
||||
}
|
||||
|
||||
emit versionErrorCb(tr("Autodiscovery failed. Unknown error when "
|
||||
"requesting .well-known."));
|
||||
nhlog::net()->error("Autodiscovery failed. Unknown error when "
|
||||
"requesting .well-known. {} {}",
|
||||
err->status_code,
|
||||
err->error_code);
|
||||
return;
|
||||
}
|
||||
|
||||
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url +
|
||||
"'");
|
||||
http::client()->set_server(res.homeserver.base_url);
|
||||
checkHomeserverVersion();
|
||||
});
|
||||
}
|
||||
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
|
||||
http::client()->set_server(res.homeserver.base_url);
|
||||
checkHomeserverVersion();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::checkHomeserverVersion()
|
||||
{
|
||||
http::client()->versions(
|
||||
[this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
if (err->status_code == 404) {
|
||||
emit versionErrorCb(tr("The required endpoints were not found. "
|
||||
"Possibly not a Matrix server."));
|
||||
return;
|
||||
}
|
||||
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
if (err->status_code == 404) {
|
||||
emit versionErrorCb(tr("The required endpoints were not found. "
|
||||
"Possibly not a Matrix server."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!err->parse_error.empty()) {
|
||||
emit versionErrorCb(tr("Received malformed response. Make sure "
|
||||
"the homeserver domain is valid."));
|
||||
return;
|
||||
}
|
||||
if (!err->parse_error.empty()) {
|
||||
emit versionErrorCb(tr("Received malformed response. Make sure "
|
||||
"the homeserver domain is valid."));
|
||||
return;
|
||||
}
|
||||
|
||||
emit versionErrorCb(tr(
|
||||
"An unknown error occured. Make sure the homeserver domain is valid."));
|
||||
return;
|
||||
emit versionErrorCb(
|
||||
tr("An unknown error occured. Make sure the homeserver domain is valid."));
|
||||
return;
|
||||
}
|
||||
|
||||
http::client()->get_login(
|
||||
[this](mtx::responses::LoginFlows flows, mtx::http::RequestErr err) {
|
||||
if (err || flows.flows.empty())
|
||||
emit versionOkCb(true, false);
|
||||
|
||||
bool ssoSupported_ = false;
|
||||
bool passwordSupported_ = false;
|
||||
for (const auto &flow : flows.flows) {
|
||||
if (flow.type == mtx::user_interactive::auth_types::sso) {
|
||||
ssoSupported_ = true;
|
||||
} else if (flow.type == mtx::user_interactive::auth_types::password) {
|
||||
passwordSupported_ = true;
|
||||
}
|
||||
|
||||
http::client()->get_login(
|
||||
[this](mtx::responses::LoginFlows flows, mtx::http::RequestErr err) {
|
||||
if (err || flows.flows.empty())
|
||||
emit versionOkCb(true, false);
|
||||
|
||||
bool ssoSupported_ = false;
|
||||
bool passwordSupported_ = false;
|
||||
for (const auto &flow : flows.flows) {
|
||||
if (flow.type == mtx::user_interactive::auth_types::sso) {
|
||||
ssoSupported_ = true;
|
||||
} else if (flow.type ==
|
||||
mtx::user_interactive::auth_types::password) {
|
||||
passwordSupported_ = true;
|
||||
}
|
||||
}
|
||||
emit versionOkCb(passwordSupported_, ssoSupported_);
|
||||
});
|
||||
}
|
||||
emit versionOkCb(passwordSupported_, ssoSupported_);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::onServerAddressEntered()
|
||||
{
|
||||
error_label_->setText("");
|
||||
http::client()->verify_certificates(
|
||||
!UserSettings::instance()->disableCertificateValidation());
|
||||
http::client()->set_server(serverInput_->text().toStdString());
|
||||
checkHomeserverVersion();
|
||||
error_label_->setText("");
|
||||
http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation());
|
||||
http::client()->set_server(serverInput_->text().toStdString());
|
||||
checkHomeserverVersion();
|
||||
|
||||
serverLayout_->removeWidget(errorIcon_);
|
||||
errorIcon_->hide();
|
||||
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
spinner_->start();
|
||||
serverLayout_->removeWidget(errorIcon_);
|
||||
errorIcon_->hide();
|
||||
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
spinner_->start();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::versionError(const QString &error)
|
||||
{
|
||||
showError(error_label_, error);
|
||||
serverInput_->show();
|
||||
showError(error_label_, error);
|
||||
serverInput_->show();
|
||||
|
||||
spinner_->stop();
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
errorIcon_->show();
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
spinner_->stop();
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
errorIcon_->show();
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::versionOk(bool passwordSupported_, bool ssoSupported_)
|
||||
{
|
||||
passwordSupported = passwordSupported_;
|
||||
ssoSupported = ssoSupported_;
|
||||
passwordSupported = passwordSupported_;
|
||||
ssoSupported = ssoSupported_;
|
||||
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
spinner_->stop();
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
spinner_->stop();
|
||||
|
||||
sso_login_button_->setVisible(ssoSupported);
|
||||
login_button_->setVisible(passwordSupported);
|
||||
sso_login_button_->setVisible(ssoSupported);
|
||||
login_button_->setVisible(passwordSupported);
|
||||
|
||||
if (serverInput_->isVisible())
|
||||
serverInput_->hide();
|
||||
if (serverInput_->isVisible())
|
||||
serverInput_->hide();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
|
||||
{
|
||||
error_label_->setText("");
|
||||
User user;
|
||||
error_label_->setText("");
|
||||
User user;
|
||||
|
||||
if (!matrixid_input_->isValid()) {
|
||||
error_matrixid_label_->show();
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
return;
|
||||
} else {
|
||||
error_matrixid_label_->setText("");
|
||||
error_matrixid_label_->hide();
|
||||
}
|
||||
if (!matrixid_input_->isValid()) {
|
||||
error_matrixid_label_->show();
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
return;
|
||||
} else {
|
||||
error_matrixid_label_->setText("");
|
||||
error_matrixid_label_->hide();
|
||||
}
|
||||
|
||||
try {
|
||||
user = parse<User>(matrixid_input_->text().toStdString());
|
||||
} catch (const std::exception &) {
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
user = parse<User>(matrixid_input_->text().toStdString());
|
||||
} catch (const std::exception &) {
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (loginMethod == LoginMethod::Password) {
|
||||
if (password_input_->text().isEmpty())
|
||||
return showError(error_label_, tr("Empty password"));
|
||||
if (loginMethod == LoginMethod::Password) {
|
||||
if (password_input_->text().isEmpty())
|
||||
return showError(error_label_, tr("Empty password"));
|
||||
|
||||
http::client()->login(
|
||||
user.localpart(),
|
||||
password_input_->text().toStdString(),
|
||||
deviceName_->text().trimmed().isEmpty() ? initialDeviceName()
|
||||
: deviceName_->text().toStdString(),
|
||||
[this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
auto error = err->matrix_error.error;
|
||||
if (error.empty())
|
||||
error = err->parse_error;
|
||||
http::client()->login(
|
||||
user.localpart(),
|
||||
password_input_->text().toStdString(),
|
||||
deviceName_->text().trimmed().isEmpty() ? initialDeviceName()
|
||||
: deviceName_->text().toStdString(),
|
||||
[this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
auto error = err->matrix_error.error;
|
||||
if (error.empty())
|
||||
error = err->parse_error;
|
||||
|
||||
showErrorMessage(error_label_, QString::fromStdString(error));
|
||||
emit errorOccurred();
|
||||
return;
|
||||
}
|
||||
showErrorMessage(error_label_, QString::fromStdString(error));
|
||||
emit errorOccurred();
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.well_known) {
|
||||
http::client()->set_server(res.well_known->homeserver.base_url);
|
||||
nhlog::net()->info("Login requested to user server: " +
|
||||
res.well_known->homeserver.base_url);
|
||||
}
|
||||
if (res.well_known) {
|
||||
http::client()->set_server(res.well_known->homeserver.base_url);
|
||||
nhlog::net()->info("Login requested to user server: " +
|
||||
res.well_known->homeserver.base_url);
|
||||
}
|
||||
|
||||
emit loginOk(res);
|
||||
});
|
||||
} else {
|
||||
auto sso = new SSOHandler();
|
||||
connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) {
|
||||
mtx::requests::Login req{};
|
||||
req.token = token;
|
||||
req.type = mtx::user_interactive::auth_types::token;
|
||||
req.device_id = deviceName_->text().trimmed().isEmpty()
|
||||
? initialDeviceName()
|
||||
: deviceName_->text().toStdString();
|
||||
http::client()->login(
|
||||
req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
showErrorMessage(
|
||||
error_label_,
|
||||
QString::fromStdString(err->matrix_error.error));
|
||||
emit errorOccurred();
|
||||
return;
|
||||
}
|
||||
emit loginOk(res);
|
||||
});
|
||||
} else {
|
||||
auto sso = new SSOHandler();
|
||||
connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) {
|
||||
mtx::requests::Login req{};
|
||||
req.token = token;
|
||||
req.type = mtx::user_interactive::auth_types::token;
|
||||
req.device_id = deviceName_->text().trimmed().isEmpty()
|
||||
? initialDeviceName()
|
||||
: deviceName_->text().toStdString();
|
||||
http::client()->login(
|
||||
req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
showErrorMessage(error_label_,
|
||||
QString::fromStdString(err->matrix_error.error));
|
||||
emit errorOccurred();
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.well_known) {
|
||||
http::client()->set_server(
|
||||
res.well_known->homeserver.base_url);
|
||||
nhlog::net()->info("Login requested to user server: " +
|
||||
res.well_known->homeserver.base_url);
|
||||
}
|
||||
if (res.well_known) {
|
||||
http::client()->set_server(res.well_known->homeserver.base_url);
|
||||
nhlog::net()->info("Login requested to user server: " +
|
||||
res.well_known->homeserver.base_url);
|
||||
}
|
||||
|
||||
emit loginOk(res);
|
||||
});
|
||||
sso->deleteLater();
|
||||
});
|
||||
connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() {
|
||||
showErrorMessage(error_label_, tr("SSO login failed"));
|
||||
emit errorOccurred();
|
||||
sso->deleteLater();
|
||||
});
|
||||
emit loginOk(res);
|
||||
});
|
||||
sso->deleteLater();
|
||||
});
|
||||
connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() {
|
||||
showErrorMessage(error_label_, tr("SSO login failed"));
|
||||
emit errorOccurred();
|
||||
sso->deleteLater();
|
||||
});
|
||||
|
||||
QDesktopServices::openUrl(
|
||||
QString::fromStdString(http::client()->login_sso_redirect(sso->url())));
|
||||
}
|
||||
QDesktopServices::openUrl(
|
||||
QString::fromStdString(http::client()->login_sso_redirect(sso->url())));
|
||||
}
|
||||
|
||||
emit loggingIn();
|
||||
emit loggingIn();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::reset()
|
||||
{
|
||||
matrixid_input_->clear();
|
||||
password_input_->clear();
|
||||
password_input_->show();
|
||||
serverInput_->clear();
|
||||
matrixid_input_->clear();
|
||||
password_input_->clear();
|
||||
password_input_->show();
|
||||
serverInput_->clear();
|
||||
|
||||
spinner_->stop();
|
||||
errorIcon_->hide();
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
serverLayout_->removeWidget(errorIcon_);
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
spinner_->stop();
|
||||
errorIcon_->hide();
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
serverLayout_->removeWidget(errorIcon_);
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
|
||||
inferredServerAddress_.clear();
|
||||
inferredServerAddress_.clear();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::onBackButtonClicked()
|
||||
{
|
||||
emit backButtonClicked();
|
||||
emit backButtonClicked();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
||||
|
|
|
|||
128
src/LoginPage.h
128
src/LoginPage.h
|
|
@ -24,101 +24,101 @@ struct Login;
|
|||
|
||||
class LoginPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class LoginMethod
|
||||
{
|
||||
Password,
|
||||
SSO,
|
||||
};
|
||||
enum class LoginMethod
|
||||
{
|
||||
Password,
|
||||
SSO,
|
||||
};
|
||||
|
||||
LoginPage(QWidget *parent = nullptr);
|
||||
LoginPage(QWidget *parent = nullptr);
|
||||
|
||||
void reset();
|
||||
void reset();
|
||||
|
||||
signals:
|
||||
void backButtonClicked();
|
||||
void loggingIn();
|
||||
void errorOccurred();
|
||||
void backButtonClicked();
|
||||
void loggingIn();
|
||||
void errorOccurred();
|
||||
|
||||
//! Used to trigger the corresponding slot outside of the main thread.
|
||||
void versionErrorCb(const QString &err);
|
||||
void versionOkCb(bool passwordSupported, bool ssoSupported);
|
||||
//! Used to trigger the corresponding slot outside of the main thread.
|
||||
void versionErrorCb(const QString &err);
|
||||
void versionOkCb(bool passwordSupported, bool ssoSupported);
|
||||
|
||||
void loginOk(const mtx::responses::Login &res);
|
||||
void showErrorMessage(QLabel *label, const QString &msg);
|
||||
void loginOk(const mtx::responses::Login &res);
|
||||
void showErrorMessage(QLabel *label, const QString &msg);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
public slots:
|
||||
// Displays errors produced during the login.
|
||||
void showError(const QString &msg);
|
||||
void showError(QLabel *label, const QString &msg);
|
||||
// Displays errors produced during the login.
|
||||
void showError(const QString &msg);
|
||||
void showError(QLabel *label, const QString &msg);
|
||||
|
||||
private slots:
|
||||
// Callback for the back button.
|
||||
void onBackButtonClicked();
|
||||
// Callback for the back button.
|
||||
void onBackButtonClicked();
|
||||
|
||||
// Callback for the login button.
|
||||
void onLoginButtonClicked(LoginMethod loginMethod);
|
||||
// Callback for the login button.
|
||||
void onLoginButtonClicked(LoginMethod loginMethod);
|
||||
|
||||
// Callback for probing the server found in the mxid
|
||||
void onMatrixIdEntered();
|
||||
// Callback for probing the server found in the mxid
|
||||
void onMatrixIdEntered();
|
||||
|
||||
// Callback for probing the manually entered server
|
||||
void onServerAddressEntered();
|
||||
// Callback for probing the manually entered server
|
||||
void onServerAddressEntered();
|
||||
|
||||
// Callback for errors produced during server probing
|
||||
void versionError(const QString &error_message);
|
||||
// Callback for successful server probing
|
||||
void versionOk(bool passwordSupported, bool ssoSupported);
|
||||
// Callback for errors produced during server probing
|
||||
void versionError(const QString &error_message);
|
||||
// Callback for successful server probing
|
||||
void versionOk(bool passwordSupported, bool ssoSupported);
|
||||
|
||||
private:
|
||||
void checkHomeserverVersion();
|
||||
std::string initialDeviceName()
|
||||
{
|
||||
void checkHomeserverVersion();
|
||||
std::string initialDeviceName()
|
||||
{
|
||||
#if defined(Q_OS_MAC)
|
||||
return "Nheko on macOS";
|
||||
return "Nheko on macOS";
|
||||
#elif defined(Q_OS_LINUX)
|
||||
return "Nheko on Linux";
|
||||
return "Nheko on Linux";
|
||||
#elif defined(Q_OS_WIN)
|
||||
return "Nheko on Windows";
|
||||
return "Nheko on Windows";
|
||||
#elif defined(Q_OS_FREEBSD)
|
||||
return "Nheko on FreeBSD";
|
||||
return "Nheko on FreeBSD";
|
||||
#else
|
||||
return "Nheko";
|
||||
return "Nheko";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
QVBoxLayout *top_layout_;
|
||||
QVBoxLayout *top_layout_;
|
||||
|
||||
QHBoxLayout *top_bar_layout_;
|
||||
QHBoxLayout *logo_layout_;
|
||||
QHBoxLayout *button_layout_;
|
||||
QHBoxLayout *top_bar_layout_;
|
||||
QHBoxLayout *logo_layout_;
|
||||
QHBoxLayout *button_layout_;
|
||||
|
||||
QLabel *logo_;
|
||||
QLabel *error_label_;
|
||||
QLabel *error_matrixid_label_;
|
||||
QLabel *logo_;
|
||||
QLabel *error_label_;
|
||||
QLabel *error_matrixid_label_;
|
||||
|
||||
QHBoxLayout *serverLayout_;
|
||||
QHBoxLayout *matrixidLayout_;
|
||||
LoadingIndicator *spinner_;
|
||||
QLabel *errorIcon_;
|
||||
QString inferredServerAddress_;
|
||||
QHBoxLayout *serverLayout_;
|
||||
QHBoxLayout *matrixidLayout_;
|
||||
LoadingIndicator *spinner_;
|
||||
QLabel *errorIcon_;
|
||||
QString inferredServerAddress_;
|
||||
|
||||
FlatButton *back_button_;
|
||||
RaisedButton *login_button_, *sso_login_button_;
|
||||
FlatButton *back_button_;
|
||||
RaisedButton *login_button_, *sso_login_button_;
|
||||
|
||||
QWidget *form_widget_;
|
||||
QHBoxLayout *form_wrapper_;
|
||||
QVBoxLayout *form_layout_;
|
||||
QWidget *form_widget_;
|
||||
QHBoxLayout *form_wrapper_;
|
||||
QVBoxLayout *form_layout_;
|
||||
|
||||
TextField *matrixid_input_;
|
||||
TextField *password_input_;
|
||||
TextField *deviceName_;
|
||||
TextField *serverInput_;
|
||||
bool passwordSupported = true;
|
||||
bool ssoSupported = false;
|
||||
TextField *matrixid_input_;
|
||||
TextField *password_input_;
|
||||
TextField *deviceName_;
|
||||
TextField *serverInput_;
|
||||
bool passwordSupported = true;
|
||||
bool ssoSupported = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,415 +43,407 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
: QMainWindow(parent)
|
||||
, userSettings_{UserSettings::instance()}
|
||||
{
|
||||
instance_ = this;
|
||||
instance_ = this;
|
||||
|
||||
setWindowTitle(0);
|
||||
setObjectName("MainWindow");
|
||||
setWindowTitle(0);
|
||||
setObjectName("MainWindow");
|
||||
|
||||
modal_ = new OverlayModal(this);
|
||||
modal_ = new OverlayModal(this);
|
||||
|
||||
restoreWindowSize();
|
||||
restoreWindowSize();
|
||||
|
||||
QFont font;
|
||||
font.setStyleStrategy(QFont::PreferAntialias);
|
||||
setFont(font);
|
||||
QFont font;
|
||||
font.setStyleStrategy(QFont::PreferAntialias);
|
||||
setFont(font);
|
||||
|
||||
trayIcon_ = new TrayIcon(":/logos/nheko.svg", this);
|
||||
trayIcon_ = new TrayIcon(":/logos/nheko.svg", this);
|
||||
|
||||
welcome_page_ = new WelcomePage(this);
|
||||
login_page_ = new LoginPage(this);
|
||||
register_page_ = new RegisterPage(this);
|
||||
chat_page_ = new ChatPage(userSettings_, this);
|
||||
userSettingsPage_ = new UserSettingsPage(userSettings_, this);
|
||||
welcome_page_ = new WelcomePage(this);
|
||||
login_page_ = new LoginPage(this);
|
||||
register_page_ = new RegisterPage(this);
|
||||
chat_page_ = new ChatPage(userSettings_, this);
|
||||
userSettingsPage_ = new UserSettingsPage(userSettings_, this);
|
||||
|
||||
// Initialize sliding widget manager.
|
||||
pageStack_ = new QStackedWidget(this);
|
||||
pageStack_->addWidget(welcome_page_);
|
||||
pageStack_->addWidget(login_page_);
|
||||
pageStack_->addWidget(register_page_);
|
||||
pageStack_->addWidget(chat_page_);
|
||||
pageStack_->addWidget(userSettingsPage_);
|
||||
// Initialize sliding widget manager.
|
||||
pageStack_ = new QStackedWidget(this);
|
||||
pageStack_->addWidget(welcome_page_);
|
||||
pageStack_->addWidget(login_page_);
|
||||
pageStack_->addWidget(register_page_);
|
||||
pageStack_->addWidget(chat_page_);
|
||||
pageStack_->addWidget(userSettingsPage_);
|
||||
|
||||
setCentralWidget(pageStack_);
|
||||
setCentralWidget(pageStack_);
|
||||
|
||||
connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
|
||||
connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
|
||||
connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
|
||||
connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
|
||||
|
||||
connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
|
||||
connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
|
||||
connect(
|
||||
register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar);
|
||||
connect(
|
||||
login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
|
||||
connect(register_page_, &RegisterPage::errorOccurred, this, [this]() {
|
||||
removeOverlayProgressBar();
|
||||
});
|
||||
connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
|
||||
connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
|
||||
connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
|
||||
connect(register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar);
|
||||
connect(login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
|
||||
connect(
|
||||
register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
|
||||
connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
|
||||
|
||||
connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage);
|
||||
connect(
|
||||
chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
|
||||
connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
|
||||
connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
|
||||
connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
|
||||
login_page_->showError(msg);
|
||||
showLoginPage();
|
||||
});
|
||||
connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage);
|
||||
connect(
|
||||
chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
|
||||
connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
|
||||
connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
|
||||
connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
|
||||
login_page_->showError(msg);
|
||||
showLoginPage();
|
||||
});
|
||||
|
||||
connect(userSettingsPage_, &UserSettingsPage::moveBack, this, [this]() {
|
||||
pageStack_->setCurrentWidget(chat_page_);
|
||||
});
|
||||
connect(userSettingsPage_, &UserSettingsPage::moveBack, this, [this]() {
|
||||
pageStack_->setCurrentWidget(chat_page_);
|
||||
});
|
||||
|
||||
connect(
|
||||
userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool)));
|
||||
connect(
|
||||
userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged);
|
||||
connect(trayIcon_,
|
||||
SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
|
||||
this,
|
||||
SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
|
||||
connect(userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool)));
|
||||
connect(
|
||||
userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged);
|
||||
connect(trayIcon_,
|
||||
SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
|
||||
this,
|
||||
SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
|
||||
|
||||
connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar()));
|
||||
connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar()));
|
||||
|
||||
connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged);
|
||||
connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged);
|
||||
|
||||
connect(
|
||||
chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage);
|
||||
connect(chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage);
|
||||
|
||||
connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
|
||||
http::client()->set_user(res.user_id);
|
||||
showChatPage();
|
||||
});
|
||||
connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
|
||||
http::client()->set_user(res.user_id);
|
||||
showChatPage();
|
||||
});
|
||||
|
||||
connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
|
||||
connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
|
||||
|
||||
QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
|
||||
connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
|
||||
QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
|
||||
connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
|
||||
|
||||
trayIcon_->setVisible(userSettings_->tray());
|
||||
trayIcon_->setVisible(userSettings_->tray());
|
||||
|
||||
// load cache on event loop
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
if (hasActiveUser()) {
|
||||
QString token = userSettings_->accessToken();
|
||||
QString home_server = userSettings_->homeserver();
|
||||
QString user_id = userSettings_->userId();
|
||||
QString device_id = userSettings_->deviceId();
|
||||
// load cache on event loop
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
if (hasActiveUser()) {
|
||||
QString token = userSettings_->accessToken();
|
||||
QString home_server = userSettings_->homeserver();
|
||||
QString user_id = userSettings_->userId();
|
||||
QString device_id = userSettings_->deviceId();
|
||||
|
||||
http::client()->set_access_token(token.toStdString());
|
||||
http::client()->set_server(home_server.toStdString());
|
||||
http::client()->set_device_id(device_id.toStdString());
|
||||
http::client()->set_access_token(token.toStdString());
|
||||
http::client()->set_server(home_server.toStdString());
|
||||
http::client()->set_device_id(device_id.toStdString());
|
||||
|
||||
try {
|
||||
using namespace mtx::identifiers;
|
||||
http::client()->set_user(parse<User>(user_id.toStdString()));
|
||||
} catch (const std::invalid_argument &) {
|
||||
nhlog::ui()->critical("bootstrapped with invalid user_id: {}",
|
||||
user_id.toStdString());
|
||||
}
|
||||
try {
|
||||
using namespace mtx::identifiers;
|
||||
http::client()->set_user(parse<User>(user_id.toStdString()));
|
||||
} catch (const std::invalid_argument &) {
|
||||
nhlog::ui()->critical("bootstrapped with invalid user_id: {}",
|
||||
user_id.toStdString());
|
||||
}
|
||||
|
||||
showChatPage();
|
||||
}
|
||||
});
|
||||
showChatPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::setWindowTitle(int notificationCount)
|
||||
{
|
||||
QString name = "nheko";
|
||||
QString name = "nheko";
|
||||
|
||||
if (!userSettings_.data()->profile().isEmpty())
|
||||
name += " | " + userSettings_.data()->profile();
|
||||
if (notificationCount > 0) {
|
||||
name.append(QString{" (%1)"}.arg(notificationCount));
|
||||
}
|
||||
QMainWindow::setWindowTitle(name);
|
||||
if (!userSettings_.data()->profile().isEmpty())
|
||||
name += " | " + userSettings_.data()->profile();
|
||||
if (notificationCount > 0) {
|
||||
name.append(QString{" (%1)"}.arg(notificationCount));
|
||||
}
|
||||
QMainWindow::setWindowTitle(name);
|
||||
}
|
||||
|
||||
bool
|
||||
MainWindow::event(QEvent *event)
|
||||
{
|
||||
auto type = event->type();
|
||||
if (type == QEvent::WindowActivate) {
|
||||
emit focusChanged(true);
|
||||
} else if (type == QEvent::WindowDeactivate) {
|
||||
emit focusChanged(false);
|
||||
}
|
||||
auto type = event->type();
|
||||
if (type == QEvent::WindowActivate) {
|
||||
emit focusChanged(true);
|
||||
} else if (type == QEvent::WindowDeactivate) {
|
||||
emit focusChanged(false);
|
||||
}
|
||||
|
||||
return QMainWindow::event(event);
|
||||
return QMainWindow::event(event);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::restoreWindowSize()
|
||||
{
|
||||
int savedWidth = userSettings_->qsettings()->value("window/width").toInt();
|
||||
int savedheight = userSettings_->qsettings()->value("window/height").toInt();
|
||||
int savedWidth = userSettings_->qsettings()->value("window/width").toInt();
|
||||
int savedheight = userSettings_->qsettings()->value("window/height").toInt();
|
||||
|
||||
nhlog::ui()->info("Restoring window size {}x{}", savedWidth, savedheight);
|
||||
nhlog::ui()->info("Restoring window size {}x{}", savedWidth, savedheight);
|
||||
|
||||
if (savedWidth == 0 || savedheight == 0)
|
||||
resize(conf::window::width, conf::window::height);
|
||||
else
|
||||
resize(savedWidth, savedheight);
|
||||
if (savedWidth == 0 || savedheight == 0)
|
||||
resize(conf::window::width, conf::window::height);
|
||||
else
|
||||
resize(savedWidth, savedheight);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::saveCurrentWindowSize()
|
||||
{
|
||||
auto settings = userSettings_->qsettings();
|
||||
QSize current = size();
|
||||
auto settings = userSettings_->qsettings();
|
||||
QSize current = size();
|
||||
|
||||
settings->setValue("window/width", current.width());
|
||||
settings->setValue("window/height", current.height());
|
||||
settings->setValue("window/width", current.width());
|
||||
settings->setValue("window/height", current.height());
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::removeOverlayProgressBar()
|
||||
{
|
||||
QTimer *timer = new QTimer(this);
|
||||
timer->setSingleShot(true);
|
||||
QTimer *timer = new QTimer(this);
|
||||
timer->setSingleShot(true);
|
||||
|
||||
connect(timer, &QTimer::timeout, [this, timer]() {
|
||||
timer->deleteLater();
|
||||
connect(timer, &QTimer::timeout, [this, timer]() {
|
||||
timer->deleteLater();
|
||||
|
||||
if (modal_)
|
||||
modal_->hide();
|
||||
if (modal_)
|
||||
modal_->hide();
|
||||
|
||||
if (spinner_)
|
||||
spinner_->stop();
|
||||
});
|
||||
if (spinner_)
|
||||
spinner_->stop();
|
||||
});
|
||||
|
||||
// FIXME: Snackbar doesn't work if it's initialized in the constructor.
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
snackBar_ = new SnackBar(this);
|
||||
connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage);
|
||||
});
|
||||
// FIXME: Snackbar doesn't work if it's initialized in the constructor.
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
snackBar_ = new SnackBar(this);
|
||||
connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage);
|
||||
});
|
||||
|
||||
timer->start(50);
|
||||
timer->start(50);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showChatPage()
|
||||
{
|
||||
auto userid = QString::fromStdString(http::client()->user_id().to_string());
|
||||
auto device_id = QString::fromStdString(http::client()->device_id());
|
||||
auto homeserver = QString::fromStdString(http::client()->server() + ":" +
|
||||
std::to_string(http::client()->port()));
|
||||
auto token = QString::fromStdString(http::client()->access_token());
|
||||
auto userid = QString::fromStdString(http::client()->user_id().to_string());
|
||||
auto device_id = QString::fromStdString(http::client()->device_id());
|
||||
auto homeserver = QString::fromStdString(http::client()->server() + ":" +
|
||||
std::to_string(http::client()->port()));
|
||||
auto token = QString::fromStdString(http::client()->access_token());
|
||||
|
||||
userSettings_.data()->setUserId(userid);
|
||||
userSettings_.data()->setAccessToken(token);
|
||||
userSettings_.data()->setDeviceId(device_id);
|
||||
userSettings_.data()->setHomeserver(homeserver);
|
||||
userSettings_.data()->setUserId(userid);
|
||||
userSettings_.data()->setAccessToken(token);
|
||||
userSettings_.data()->setDeviceId(device_id);
|
||||
userSettings_.data()->setHomeserver(homeserver);
|
||||
|
||||
showOverlayProgressBar();
|
||||
showOverlayProgressBar();
|
||||
|
||||
pageStack_->setCurrentWidget(chat_page_);
|
||||
pageStack_->setCurrentWidget(chat_page_);
|
||||
|
||||
pageStack_->removeWidget(welcome_page_);
|
||||
pageStack_->removeWidget(login_page_);
|
||||
pageStack_->removeWidget(register_page_);
|
||||
pageStack_->removeWidget(welcome_page_);
|
||||
pageStack_->removeWidget(login_page_);
|
||||
pageStack_->removeWidget(register_page_);
|
||||
|
||||
login_page_->reset();
|
||||
chat_page_->bootstrap(userid, homeserver, token);
|
||||
connect(cache::client(),
|
||||
&Cache::secretChanged,
|
||||
userSettingsPage_,
|
||||
&UserSettingsPage::updateSecretStatus);
|
||||
emit reload();
|
||||
login_page_->reset();
|
||||
chat_page_->bootstrap(userid, homeserver, token);
|
||||
connect(cache::client(),
|
||||
&Cache::secretChanged,
|
||||
userSettingsPage_,
|
||||
&UserSettingsPage::updateSecretStatus);
|
||||
emit reload();
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
|
||||
if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") !=
|
||||
QMessageBox::Yes) {
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
|
||||
if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") !=
|
||||
QMessageBox::Yes) {
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() &&
|
||||
userSettings_->tray()) {
|
||||
event->ignore();
|
||||
hide();
|
||||
}
|
||||
if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() && userSettings_->tray()) {
|
||||
event->ignore();
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
|
||||
{
|
||||
switch (reason) {
|
||||
case QSystemTrayIcon::Trigger:
|
||||
if (!isVisible()) {
|
||||
show();
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
switch (reason) {
|
||||
case QSystemTrayIcon::Trigger:
|
||||
if (!isVisible()) {
|
||||
show();
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MainWindow::hasActiveUser()
|
||||
{
|
||||
auto settings = userSettings_->qsettings();
|
||||
QString prefix;
|
||||
if (userSettings_->profile() != "")
|
||||
prefix = "profile/" + userSettings_->profile() + "/";
|
||||
auto settings = userSettings_->qsettings();
|
||||
QString prefix;
|
||||
if (userSettings_->profile() != "")
|
||||
prefix = "profile/" + userSettings_->profile() + "/";
|
||||
|
||||
return settings->contains(prefix + "auth/access_token") &&
|
||||
settings->contains(prefix + "auth/home_server") &&
|
||||
settings->contains(prefix + "auth/user_id");
|
||||
return settings->contains(prefix + "auth/access_token") &&
|
||||
settings->contains(prefix + "auth/home_server") &&
|
||||
settings->contains(prefix + "auth/user_id");
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::openLeaveRoomDialog(const QString &room_id)
|
||||
{
|
||||
auto dialog = new dialogs::LeaveRoom(this);
|
||||
connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, room_id]() {
|
||||
chat_page_->leaveRoom(room_id);
|
||||
});
|
||||
auto dialog = new dialogs::LeaveRoom(this);
|
||||
connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, room_id]() {
|
||||
chat_page_->leaveRoom(room_id);
|
||||
});
|
||||
|
||||
showDialog(dialog);
|
||||
showDialog(dialog);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showOverlayProgressBar()
|
||||
{
|
||||
spinner_ = new LoadingIndicator(this);
|
||||
spinner_->setFixedHeight(100);
|
||||
spinner_->setFixedWidth(100);
|
||||
spinner_->setObjectName("ChatPageLoadSpinner");
|
||||
spinner_->start();
|
||||
spinner_ = new LoadingIndicator(this);
|
||||
spinner_->setFixedHeight(100);
|
||||
spinner_->setFixedWidth(100);
|
||||
spinner_->setObjectName("ChatPageLoadSpinner");
|
||||
spinner_->start();
|
||||
|
||||
showSolidOverlayModal(spinner_);
|
||||
showSolidOverlayModal(spinner_);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::openJoinRoomDialog(std::function<void(const QString &room_id)> callback)
|
||||
{
|
||||
auto dialog = new dialogs::JoinRoom(this);
|
||||
connect(dialog, &dialogs::JoinRoom::joinRoom, this, [callback](const QString &room) {
|
||||
if (!room.isEmpty())
|
||||
callback(room);
|
||||
});
|
||||
auto dialog = new dialogs::JoinRoom(this);
|
||||
connect(dialog, &dialogs::JoinRoom::joinRoom, this, [callback](const QString &room) {
|
||||
if (!room.isEmpty())
|
||||
callback(room);
|
||||
});
|
||||
|
||||
showDialog(dialog);
|
||||
showDialog(dialog);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::openCreateRoomDialog(
|
||||
std::function<void(const mtx::requests::CreateRoom &request)> callback)
|
||||
{
|
||||
auto dialog = new dialogs::CreateRoom(this);
|
||||
connect(dialog,
|
||||
&dialogs::CreateRoom::createRoom,
|
||||
this,
|
||||
[callback](const mtx::requests::CreateRoom &request) { callback(request); });
|
||||
auto dialog = new dialogs::CreateRoom(this);
|
||||
connect(dialog,
|
||||
&dialogs::CreateRoom::createRoom,
|
||||
this,
|
||||
[callback](const mtx::requests::CreateRoom &request) { callback(request); });
|
||||
|
||||
showDialog(dialog);
|
||||
showDialog(dialog);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showTransparentOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags)
|
||||
{
|
||||
modal_->setWidget(content);
|
||||
modal_->setColor(QColor(30, 30, 30, 150));
|
||||
modal_->setDismissible(true);
|
||||
modal_->setContentAlignment(flags);
|
||||
modal_->raise();
|
||||
modal_->show();
|
||||
modal_->setWidget(content);
|
||||
modal_->setColor(QColor(30, 30, 30, 150));
|
||||
modal_->setDismissible(true);
|
||||
modal_->setContentAlignment(flags);
|
||||
modal_->raise();
|
||||
modal_->show();
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags)
|
||||
{
|
||||
modal_->setWidget(content);
|
||||
modal_->setColor(QColor(30, 30, 30));
|
||||
modal_->setDismissible(false);
|
||||
modal_->setContentAlignment(flags);
|
||||
modal_->raise();
|
||||
modal_->show();
|
||||
modal_->setWidget(content);
|
||||
modal_->setColor(QColor(30, 30, 30));
|
||||
modal_->setDismissible(false);
|
||||
modal_->setContentAlignment(flags);
|
||||
modal_->raise();
|
||||
modal_->show();
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::openLogoutDialog()
|
||||
{
|
||||
auto dialog = new dialogs::Logout(this);
|
||||
connect(dialog, &dialogs::Logout::loggingOut, this, [this]() {
|
||||
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
|
||||
if (QMessageBox::question(
|
||||
this, "nheko", "A call is in progress. Log out?") !=
|
||||
QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
WebRTCSession::instance().end();
|
||||
}
|
||||
chat_page_->initiateLogout();
|
||||
});
|
||||
auto dialog = new dialogs::Logout(this);
|
||||
connect(dialog, &dialogs::Logout::loggingOut, this, [this]() {
|
||||
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
|
||||
if (QMessageBox::question(this, "nheko", "A call is in progress. Log out?") !=
|
||||
QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
WebRTCSession::instance().end();
|
||||
}
|
||||
chat_page_->initiateLogout();
|
||||
});
|
||||
|
||||
showDialog(dialog);
|
||||
showDialog(dialog);
|
||||
}
|
||||
|
||||
bool
|
||||
MainWindow::hasActiveDialogs() const
|
||||
{
|
||||
return !modal_ && modal_->isVisible();
|
||||
return !modal_ && modal_->isVisible();
|
||||
}
|
||||
|
||||
bool
|
||||
MainWindow::pageSupportsTray() const
|
||||
{
|
||||
return !welcome_page_->isVisible() && !login_page_->isVisible() &&
|
||||
!register_page_->isVisible();
|
||||
return !welcome_page_->isVisible() && !login_page_->isVisible() && !register_page_->isVisible();
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::hideOverlay()
|
||||
{
|
||||
if (modal_)
|
||||
modal_->hide();
|
||||
if (modal_)
|
||||
modal_->hide();
|
||||
}
|
||||
|
||||
inline void
|
||||
MainWindow::showDialog(QWidget *dialog)
|
||||
{
|
||||
utils::centerWidget(dialog, this);
|
||||
dialog->raise();
|
||||
dialog->show();
|
||||
utils::centerWidget(dialog, this);
|
||||
dialog->raise();
|
||||
dialog->show();
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showWelcomePage()
|
||||
{
|
||||
removeOverlayProgressBar();
|
||||
pageStack_->addWidget(welcome_page_);
|
||||
pageStack_->setCurrentWidget(welcome_page_);
|
||||
removeOverlayProgressBar();
|
||||
pageStack_->addWidget(welcome_page_);
|
||||
pageStack_->setCurrentWidget(welcome_page_);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showLoginPage()
|
||||
{
|
||||
if (modal_)
|
||||
modal_->hide();
|
||||
if (modal_)
|
||||
modal_->hide();
|
||||
|
||||
pageStack_->addWidget(login_page_);
|
||||
pageStack_->setCurrentWidget(login_page_);
|
||||
pageStack_->addWidget(login_page_);
|
||||
pageStack_->setCurrentWidget(login_page_);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showRegisterPage()
|
||||
{
|
||||
pageStack_->addWidget(register_page_);
|
||||
pageStack_->setCurrentWidget(register_page_);
|
||||
pageStack_->addWidget(register_page_);
|
||||
pageStack_->setCurrentWidget(register_page_);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showUserSettingsPage()
|
||||
{
|
||||
pageStack_->setCurrentWidget(userSettingsPage_);
|
||||
pageStack_->setCurrentWidget(userSettingsPage_);
|
||||
}
|
||||
|
|
|
|||
131
src/MainWindow.h
131
src/MainWindow.h
|
|
@ -46,93 +46,92 @@ class ReCaptcha;
|
|||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(int x READ x CONSTANT)
|
||||
Q_PROPERTY(int y READ y CONSTANT)
|
||||
Q_PROPERTY(int width READ width CONSTANT)
|
||||
Q_PROPERTY(int height READ height CONSTANT)
|
||||
Q_PROPERTY(int x READ x CONSTANT)
|
||||
Q_PROPERTY(int y READ y CONSTANT)
|
||||
Q_PROPERTY(int width READ width CONSTANT)
|
||||
Q_PROPERTY(int height READ height CONSTANT)
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
|
||||
static MainWindow *instance() { return instance_; }
|
||||
void saveCurrentWindowSize();
|
||||
static MainWindow *instance() { return instance_; }
|
||||
void saveCurrentWindowSize();
|
||||
|
||||
void openLeaveRoomDialog(const QString &room_id);
|
||||
void openInviteUsersDialog(std::function<void(const QStringList &invitees)> callback);
|
||||
void openCreateRoomDialog(
|
||||
std::function<void(const mtx::requests::CreateRoom &request)> callback);
|
||||
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
|
||||
void openLogoutDialog();
|
||||
void openLeaveRoomDialog(const QString &room_id);
|
||||
void openInviteUsersDialog(std::function<void(const QStringList &invitees)> callback);
|
||||
void openCreateRoomDialog(
|
||||
std::function<void(const mtx::requests::CreateRoom &request)> callback);
|
||||
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
|
||||
void openLogoutDialog();
|
||||
|
||||
void hideOverlay();
|
||||
void showSolidOverlayModal(QWidget *content,
|
||||
QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter);
|
||||
void showTransparentOverlayModal(QWidget *content,
|
||||
QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop |
|
||||
Qt::AlignHCenter);
|
||||
void hideOverlay();
|
||||
void showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter);
|
||||
void showTransparentOverlayModal(QWidget *content,
|
||||
QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop |
|
||||
Qt::AlignHCenter);
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
bool event(QEvent *event) override;
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
private slots:
|
||||
//! Handle interaction with the tray icon.
|
||||
void iconActivated(QSystemTrayIcon::ActivationReason reason);
|
||||
//! Handle interaction with the tray icon.
|
||||
void iconActivated(QSystemTrayIcon::ActivationReason reason);
|
||||
|
||||
//! Show the welcome page in the main window.
|
||||
void showWelcomePage();
|
||||
//! Show the welcome page in the main window.
|
||||
void showWelcomePage();
|
||||
|
||||
//! Show the login page in the main window.
|
||||
void showLoginPage();
|
||||
//! Show the login page in the main window.
|
||||
void showLoginPage();
|
||||
|
||||
//! Show the register page in the main window.
|
||||
void showRegisterPage();
|
||||
//! Show the register page in the main window.
|
||||
void showRegisterPage();
|
||||
|
||||
//! Show user settings page.
|
||||
void showUserSettingsPage();
|
||||
//! Show user settings page.
|
||||
void showUserSettingsPage();
|
||||
|
||||
//! Show the chat page and start communicating with the given access token.
|
||||
void showChatPage();
|
||||
//! Show the chat page and start communicating with the given access token.
|
||||
void showChatPage();
|
||||
|
||||
void showOverlayProgressBar();
|
||||
void removeOverlayProgressBar();
|
||||
void showOverlayProgressBar();
|
||||
void removeOverlayProgressBar();
|
||||
|
||||
virtual void setWindowTitle(int notificationCount);
|
||||
virtual void setWindowTitle(int notificationCount);
|
||||
|
||||
signals:
|
||||
void focusChanged(const bool focused);
|
||||
void reload();
|
||||
void focusChanged(const bool focused);
|
||||
void reload();
|
||||
|
||||
private:
|
||||
void showDialog(QWidget *dialog);
|
||||
bool hasActiveUser();
|
||||
void restoreWindowSize();
|
||||
//! Check if there is an open dialog.
|
||||
bool hasActiveDialogs() const;
|
||||
//! Check if the current page supports the "minimize to tray" functionality.
|
||||
bool pageSupportsTray() const;
|
||||
void showDialog(QWidget *dialog);
|
||||
bool hasActiveUser();
|
||||
void restoreWindowSize();
|
||||
//! Check if there is an open dialog.
|
||||
bool hasActiveDialogs() const;
|
||||
//! Check if the current page supports the "minimize to tray" functionality.
|
||||
bool pageSupportsTray() const;
|
||||
|
||||
static MainWindow *instance_;
|
||||
static MainWindow *instance_;
|
||||
|
||||
//! The initial welcome screen.
|
||||
WelcomePage *welcome_page_;
|
||||
//! The login screen.
|
||||
LoginPage *login_page_;
|
||||
//! The register page.
|
||||
RegisterPage *register_page_;
|
||||
//! A stacked widget that handles the transitions between widgets.
|
||||
QStackedWidget *pageStack_;
|
||||
//! The main chat area.
|
||||
ChatPage *chat_page_;
|
||||
UserSettingsPage *userSettingsPage_;
|
||||
QSharedPointer<UserSettings> userSettings_;
|
||||
//! Tray icon that shows the unread message count.
|
||||
TrayIcon *trayIcon_;
|
||||
//! Notifications display.
|
||||
SnackBar *snackBar_ = nullptr;
|
||||
//! Overlay modal used to project other widgets.
|
||||
OverlayModal *modal_ = nullptr;
|
||||
LoadingIndicator *spinner_ = nullptr;
|
||||
//! The initial welcome screen.
|
||||
WelcomePage *welcome_page_;
|
||||
//! The login screen.
|
||||
LoginPage *login_page_;
|
||||
//! The register page.
|
||||
RegisterPage *register_page_;
|
||||
//! A stacked widget that handles the transitions between widgets.
|
||||
QStackedWidget *pageStack_;
|
||||
//! The main chat area.
|
||||
ChatPage *chat_page_;
|
||||
UserSettingsPage *userSettingsPage_;
|
||||
QSharedPointer<UserSettings> userSettings_;
|
||||
//! Tray icon that shows the unread message count.
|
||||
TrayIcon *trayIcon_;
|
||||
//! Notifications display.
|
||||
SnackBar *snackBar_ = nullptr;
|
||||
//! Overlay modal used to project other widgets.
|
||||
OverlayModal *modal_ = nullptr;
|
||||
LoadingIndicator *spinner_ = nullptr;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -37,31 +37,31 @@ namespace http {
|
|||
mtx::http::Client *
|
||||
client()
|
||||
{
|
||||
return client_.get();
|
||||
return client_.get();
|
||||
}
|
||||
|
||||
bool
|
||||
is_logged_in()
|
||||
{
|
||||
return !client_->access_token().empty();
|
||||
return !client_->access_token().empty();
|
||||
}
|
||||
|
||||
void
|
||||
init()
|
||||
{
|
||||
qRegisterMetaType<mtx::responses::Login>();
|
||||
qRegisterMetaType<mtx::responses::Messages>();
|
||||
qRegisterMetaType<mtx::responses::Notifications>();
|
||||
qRegisterMetaType<mtx::responses::Rooms>();
|
||||
qRegisterMetaType<mtx::responses::Sync>();
|
||||
qRegisterMetaType<mtx::responses::JoinedGroups>();
|
||||
qRegisterMetaType<mtx::responses::GroupProfile>();
|
||||
qRegisterMetaType<std::string>();
|
||||
qRegisterMetaType<nlohmann::json>();
|
||||
qRegisterMetaType<std::vector<std::string>>();
|
||||
qRegisterMetaType<std::vector<QString>>();
|
||||
qRegisterMetaType<std::map<QString, bool>>("std::map<QString, bool>");
|
||||
qRegisterMetaType<std::set<QString>>();
|
||||
qRegisterMetaType<mtx::responses::Login>();
|
||||
qRegisterMetaType<mtx::responses::Messages>();
|
||||
qRegisterMetaType<mtx::responses::Notifications>();
|
||||
qRegisterMetaType<mtx::responses::Rooms>();
|
||||
qRegisterMetaType<mtx::responses::Sync>();
|
||||
qRegisterMetaType<mtx::responses::JoinedGroups>();
|
||||
qRegisterMetaType<mtx::responses::GroupProfile>();
|
||||
qRegisterMetaType<std::string>();
|
||||
qRegisterMetaType<nlohmann::json>();
|
||||
qRegisterMetaType<std::vector<std::string>>();
|
||||
qRegisterMetaType<std::vector<QString>>();
|
||||
qRegisterMetaType<std::map<QString, bool>>("std::map<QString, bool>");
|
||||
qRegisterMetaType<std::set<QString>>();
|
||||
}
|
||||
|
||||
} // namespace http
|
||||
|
|
|
|||
|
|
@ -15,98 +15,96 @@ MemberList::MemberList(const QString &room_id, QObject *parent)
|
|||
: QAbstractListModel{parent}
|
||||
, room_id_{room_id}
|
||||
{
|
||||
try {
|
||||
info_ = cache::singleRoomInfo(room_id_.toStdString());
|
||||
} catch (const lmdb::error &) {
|
||||
nhlog::db()->warn("failed to retrieve room info from cache: {}",
|
||||
room_id_.toStdString());
|
||||
}
|
||||
try {
|
||||
info_ = cache::singleRoomInfo(room_id_.toStdString());
|
||||
} catch (const lmdb::error &) {
|
||||
nhlog::db()->warn("failed to retrieve room info from cache: {}", room_id_.toStdString());
|
||||
}
|
||||
|
||||
try {
|
||||
auto members = cache::getMembers(room_id_.toStdString());
|
||||
addUsers(members);
|
||||
numUsersLoaded_ = members.size();
|
||||
} catch (const lmdb::error &e) {
|
||||
nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what());
|
||||
}
|
||||
try {
|
||||
auto members = cache::getMembers(room_id_.toStdString());
|
||||
addUsers(members);
|
||||
numUsersLoaded_ = members.size();
|
||||
} catch (const lmdb::error &e) {
|
||||
nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MemberList::addUsers(const std::vector<RoomMember> &members)
|
||||
{
|
||||
beginInsertRows(
|
||||
QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1);
|
||||
beginInsertRows(QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1);
|
||||
|
||||
for (const auto &member : members)
|
||||
m_memberList.push_back(
|
||||
{member,
|
||||
ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl(
|
||||
member.user_id)});
|
||||
for (const auto &member : members)
|
||||
m_memberList.push_back(
|
||||
{member,
|
||||
ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl(
|
||||
member.user_id)});
|
||||
|
||||
endInsertRows();
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
MemberList::roleNames() const
|
||||
{
|
||||
return {
|
||||
{Mxid, "mxid"},
|
||||
{DisplayName, "displayName"},
|
||||
{AvatarUrl, "avatarUrl"},
|
||||
{Trustlevel, "trustlevel"},
|
||||
};
|
||||
return {
|
||||
{Mxid, "mxid"},
|
||||
{DisplayName, "displayName"},
|
||||
{AvatarUrl, "avatarUrl"},
|
||||
{Trustlevel, "trustlevel"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
MemberList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0)
|
||||
return {};
|
||||
if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0)
|
||||
return {};
|
||||
|
||||
switch (role) {
|
||||
case Mxid:
|
||||
return m_memberList[index.row()].first.user_id;
|
||||
case DisplayName:
|
||||
return m_memberList[index.row()].first.display_name;
|
||||
case AvatarUrl:
|
||||
return m_memberList[index.row()].second;
|
||||
case Trustlevel: {
|
||||
auto stat =
|
||||
cache::verificationStatus(m_memberList[index.row()].first.user_id.toStdString());
|
||||
switch (role) {
|
||||
case Mxid:
|
||||
return m_memberList[index.row()].first.user_id;
|
||||
case DisplayName:
|
||||
return m_memberList[index.row()].first.display_name;
|
||||
case AvatarUrl:
|
||||
return m_memberList[index.row()].second;
|
||||
case Trustlevel: {
|
||||
auto stat =
|
||||
cache::verificationStatus(m_memberList[index.row()].first.user_id.toStdString());
|
||||
|
||||
if (!stat)
|
||||
return crypto::Unverified;
|
||||
if (stat->unverified_device_count)
|
||||
return crypto::Unverified;
|
||||
else
|
||||
return stat->user_verified;
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
if (!stat)
|
||||
return crypto::Unverified;
|
||||
if (stat->unverified_device_count)
|
||||
return crypto::Unverified;
|
||||
else
|
||||
return stat->user_verified;
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MemberList::canFetchMore(const QModelIndex &) const
|
||||
{
|
||||
const size_t numMembers = rowCount();
|
||||
if (numMembers > 1 && numMembers < info_.member_count)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
const size_t numMembers = rowCount();
|
||||
if (numMembers > 1 && numMembers < info_.member_count)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
MemberList::fetchMore(const QModelIndex &)
|
||||
{
|
||||
loadingMoreMembers_ = true;
|
||||
emit loadingMoreMembersChanged();
|
||||
loadingMoreMembers_ = true;
|
||||
emit loadingMoreMembersChanged();
|
||||
|
||||
auto members = cache::getMembers(room_id_.toStdString(), rowCount());
|
||||
addUsers(members);
|
||||
numUsersLoaded_ += members.size();
|
||||
emit numUsersLoadedChanged();
|
||||
auto members = cache::getMembers(room_id_.toStdString(), rowCount());
|
||||
addUsers(members);
|
||||
numUsersLoaded_ += members.size();
|
||||
emit numUsersLoadedChanged();
|
||||
|
||||
loadingMoreMembers_ = false;
|
||||
emit loadingMoreMembersChanged();
|
||||
loadingMoreMembers_ = false;
|
||||
emit loadingMoreMembersChanged();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,59 +10,59 @@
|
|||
|
||||
class MemberList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
|
||||
Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged)
|
||||
Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
|
||||
Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged)
|
||||
Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged)
|
||||
Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged)
|
||||
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
|
||||
Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged)
|
||||
Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
|
||||
Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged)
|
||||
Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged)
|
||||
Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged)
|
||||
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
Mxid,
|
||||
DisplayName,
|
||||
AvatarUrl,
|
||||
Trustlevel,
|
||||
};
|
||||
MemberList(const QString &room_id, QObject *parent = nullptr);
|
||||
enum Roles
|
||||
{
|
||||
Mxid,
|
||||
DisplayName,
|
||||
AvatarUrl,
|
||||
Trustlevel,
|
||||
};
|
||||
MemberList(const QString &room_id, QObject *parent = nullptr);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return static_cast<int>(m_memberList.size());
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return static_cast<int>(m_memberList.size());
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
QString roomName() const { return QString::fromStdString(info_.name); }
|
||||
int memberCount() const { return info_.member_count; }
|
||||
QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); }
|
||||
QString roomId() const { return room_id_; }
|
||||
int numUsersLoaded() const { return numUsersLoaded_; }
|
||||
bool loadingMoreMembers() const { return loadingMoreMembers_; }
|
||||
QString roomName() const { return QString::fromStdString(info_.name); }
|
||||
int memberCount() const { return info_.member_count; }
|
||||
QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); }
|
||||
QString roomId() const { return room_id_; }
|
||||
int numUsersLoaded() const { return numUsersLoaded_; }
|
||||
bool loadingMoreMembers() const { return loadingMoreMembers_; }
|
||||
|
||||
signals:
|
||||
void roomNameChanged();
|
||||
void memberCountChanged();
|
||||
void avatarUrlChanged();
|
||||
void roomIdChanged();
|
||||
void numUsersLoadedChanged();
|
||||
void loadingMoreMembersChanged();
|
||||
void roomNameChanged();
|
||||
void memberCountChanged();
|
||||
void avatarUrlChanged();
|
||||
void roomIdChanged();
|
||||
void numUsersLoadedChanged();
|
||||
void loadingMoreMembersChanged();
|
||||
|
||||
public slots:
|
||||
void addUsers(const std::vector<RoomMember> &users);
|
||||
void addUsers(const std::vector<RoomMember> &users);
|
||||
|
||||
protected:
|
||||
bool canFetchMore(const QModelIndex &) const override;
|
||||
void fetchMore(const QModelIndex &) override;
|
||||
bool canFetchMore(const QModelIndex &) const override;
|
||||
void fetchMore(const QModelIndex &) override;
|
||||
|
||||
private:
|
||||
QVector<QPair<RoomMember, QString>> m_memberList;
|
||||
QString room_id_;
|
||||
RoomInfo info_;
|
||||
int numUsersLoaded_{0};
|
||||
bool loadingMoreMembers_{false};
|
||||
QVector<QPair<RoomMember, QString>> m_memberList;
|
||||
QString room_id_;
|
||||
RoomInfo info_;
|
||||
int numUsersLoaded_{0};
|
||||
bool loadingMoreMembers_{false};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,70 +24,70 @@ QHash<QString, mtx::crypto::EncryptedFile> infos;
|
|||
QQuickImageResponse *
|
||||
MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
||||
{
|
||||
auto id_ = id;
|
||||
bool crop = true;
|
||||
double radius = 0;
|
||||
auto id_ = id;
|
||||
bool crop = true;
|
||||
double radius = 0;
|
||||
|
||||
auto queryStart = id.lastIndexOf('?');
|
||||
if (queryStart != -1) {
|
||||
id_ = id.left(queryStart);
|
||||
auto query = id.midRef(queryStart + 1);
|
||||
auto queryBits = query.split('&');
|
||||
auto queryStart = id.lastIndexOf('?');
|
||||
if (queryStart != -1) {
|
||||
id_ = id.left(queryStart);
|
||||
auto query = id.midRef(queryStart + 1);
|
||||
auto queryBits = query.split('&');
|
||||
|
||||
for (auto b : queryBits) {
|
||||
if (b == "scale") {
|
||||
crop = false;
|
||||
} else if (b.startsWith("radius=")) {
|
||||
radius = b.mid(7).toDouble();
|
||||
}
|
||||
}
|
||||
for (auto b : queryBits) {
|
||||
if (b == "scale") {
|
||||
crop = false;
|
||||
} else if (b.startsWith("radius=")) {
|
||||
radius = b.mid(7).toDouble();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MxcImageResponse *response = new MxcImageResponse(id_, crop, radius, requestedSize);
|
||||
pool.start(response);
|
||||
return response;
|
||||
MxcImageResponse *response = new MxcImageResponse(id_, crop, radius, requestedSize);
|
||||
pool.start(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
void
|
||||
MxcImageProvider::addEncryptionInfo(mtx::crypto::EncryptedFile info)
|
||||
{
|
||||
infos.insert(QString::fromStdString(info.url), info);
|
||||
infos.insert(QString::fromStdString(info.url), info);
|
||||
}
|
||||
void
|
||||
MxcImageResponse::run()
|
||||
{
|
||||
MxcImageProvider::download(
|
||||
m_id,
|
||||
m_requestedSize,
|
||||
[this](QString, QSize, QImage image, QString) {
|
||||
if (image.isNull()) {
|
||||
m_error = "Failed to download image.";
|
||||
} else {
|
||||
m_image = image;
|
||||
}
|
||||
emit finished();
|
||||
},
|
||||
m_crop,
|
||||
m_radius);
|
||||
MxcImageProvider::download(
|
||||
m_id,
|
||||
m_requestedSize,
|
||||
[this](QString, QSize, QImage image, QString) {
|
||||
if (image.isNull()) {
|
||||
m_error = "Failed to download image.";
|
||||
} else {
|
||||
m_image = image;
|
||||
}
|
||||
emit finished();
|
||||
},
|
||||
m_crop,
|
||||
m_radius);
|
||||
}
|
||||
|
||||
static QImage
|
||||
clipRadius(QImage img, double radius)
|
||||
{
|
||||
QImage out(img.size(), QImage::Format_ARGB32_Premultiplied);
|
||||
out.fill(Qt::transparent);
|
||||
QImage out(img.size(), QImage::Format_ARGB32_Premultiplied);
|
||||
out.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&out);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
QPainter painter(&out);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
|
||||
QPainterPath ppath;
|
||||
ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize);
|
||||
QPainterPath ppath;
|
||||
ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize);
|
||||
|
||||
painter.setClipPath(ppath);
|
||||
painter.drawImage(img.rect(), img);
|
||||
painter.setClipPath(ppath);
|
||||
painter.drawImage(img.rect(), img);
|
||||
|
||||
return out;
|
||||
return out;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -97,187 +97,165 @@ MxcImageProvider::download(const QString &id,
|
|||
bool crop,
|
||||
double radius)
|
||||
{
|
||||
std::optional<mtx::crypto::EncryptedFile> encryptionInfo;
|
||||
auto temp = infos.find("mxc://" + id);
|
||||
if (temp != infos.end())
|
||||
encryptionInfo = *temp;
|
||||
std::optional<mtx::crypto::EncryptedFile> encryptionInfo;
|
||||
auto temp = infos.find("mxc://" + id);
|
||||
if (temp != infos.end())
|
||||
encryptionInfo = *temp;
|
||||
|
||||
if (requestedSize.isValid() && !encryptionInfo) {
|
||||
QString fileName =
|
||||
QString("%1_%2x%3_%4_radius%5")
|
||||
.arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding |
|
||||
QByteArray::OmitTrailingEquals)))
|
||||
.arg(requestedSize.width())
|
||||
.arg(requestedSize.height())
|
||||
.arg(crop ? "crop" : "scale")
|
||||
.arg(radius);
|
||||
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||
"/media_cache",
|
||||
fileName);
|
||||
QDir().mkpath(fileInfo.absolutePath());
|
||||
if (requestedSize.isValid() && !encryptionInfo) {
|
||||
QString fileName = QString("%1_%2x%3_%4_radius%5")
|
||||
.arg(QString::fromUtf8(id.toUtf8().toBase64(
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)))
|
||||
.arg(requestedSize.width())
|
||||
.arg(requestedSize.height())
|
||||
.arg(crop ? "crop" : "scale")
|
||||
.arg(radius);
|
||||
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||
"/media_cache",
|
||||
fileName);
|
||||
QDir().mkpath(fileInfo.absolutePath());
|
||||
|
||||
if (fileInfo.exists()) {
|
||||
QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath());
|
||||
if (!image.isNull()) {
|
||||
image = image.scaled(
|
||||
requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
if (fileInfo.exists()) {
|
||||
QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath());
|
||||
if (!image.isNull()) {
|
||||
image = image.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
if (!image.isNull()) {
|
||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
mtx::http::ThumbOpts opts;
|
||||
opts.mxc_url = "mxc://" + id.toStdString();
|
||||
opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1;
|
||||
opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1;
|
||||
opts.method = crop ? "crop" : "scale";
|
||||
http::client()->get_thumbnail(
|
||||
opts,
|
||||
[fileInfo, requestedSize, radius, then, id](const std::string &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err || res.empty()) {
|
||||
then(id, QSize(), {}, "");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = QByteArray(res.data(), (int)res.size());
|
||||
QImage image = utils::readImage(data);
|
||||
if (!image.isNull()) {
|
||||
image = image.scaled(
|
||||
requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
}
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
if (image.save(fileInfo.absoluteFilePath(), "png"))
|
||||
nhlog::ui()->debug("Wrote: {}",
|
||||
fileInfo.absoluteFilePath().toStdString());
|
||||
else
|
||||
nhlog::ui()->debug("Failed to write: {}",
|
||||
fileInfo.absoluteFilePath().toStdString());
|
||||
|
||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
QString fileName =
|
||||
QString("%1_radius%2")
|
||||
.arg(QString::fromUtf8(id.toUtf8().toBase64(
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)))
|
||||
.arg(radius);
|
||||
|
||||
QFileInfo fileInfo(
|
||||
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||
"/media_cache",
|
||||
fileName);
|
||||
QDir().mkpath(fileInfo.absolutePath());
|
||||
|
||||
if (fileInfo.exists()) {
|
||||
if (encryptionInfo) {
|
||||
QFile f(fileInfo.absoluteFilePath());
|
||||
f.open(QIODevice::ReadOnly);
|
||||
|
||||
QByteArray fileData = f.readAll();
|
||||
auto tempData =
|
||||
mtx::crypto::to_string(mtx::crypto::decrypt_file(
|
||||
fileData.toStdString(), encryptionInfo.value()));
|
||||
auto data =
|
||||
QByteArray(tempData.data(), (int)tempData.size());
|
||||
QImage image = utils::readImage(data);
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
if (!image.isNull()) {
|
||||
if (radius != 0) {
|
||||
image =
|
||||
clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
then(id,
|
||||
requestedSize,
|
||||
image,
|
||||
fileInfo.absoluteFilePath());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
QImage image =
|
||||
utils::readImageFromFile(fileInfo.absoluteFilePath());
|
||||
if (!image.isNull()) {
|
||||
if (radius != 0) {
|
||||
image =
|
||||
clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
then(id,
|
||||
requestedSize,
|
||||
image,
|
||||
fileInfo.absoluteFilePath());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http::client()->download(
|
||||
"mxc://" + id.toStdString(),
|
||||
[fileInfo, requestedSize, then, id, radius, encryptionInfo](
|
||||
const std::string &res,
|
||||
const std::string &,
|
||||
const std::string &originalFilename,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
then(id, QSize(), {}, "");
|
||||
return;
|
||||
}
|
||||
|
||||
auto tempData = res;
|
||||
QFile f(fileInfo.absoluteFilePath());
|
||||
if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) {
|
||||
then(id, QSize(), {}, "");
|
||||
return;
|
||||
}
|
||||
f.write(tempData.data(), tempData.size());
|
||||
f.close();
|
||||
|
||||
if (encryptionInfo) {
|
||||
tempData =
|
||||
mtx::crypto::to_string(mtx::crypto::decrypt_file(
|
||||
tempData, encryptionInfo.value()));
|
||||
auto data =
|
||||
QByteArray(tempData.data(), (int)tempData.size());
|
||||
QImage image = utils::readImage(data);
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
image.setText("original filename",
|
||||
QString::fromStdString(originalFilename));
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
then(
|
||||
id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
return;
|
||||
}
|
||||
|
||||
QImage image =
|
||||
utils::readImageFromFile(fileInfo.absoluteFilePath());
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
image.setText("original filename",
|
||||
QString::fromStdString(originalFilename));
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
});
|
||||
} catch (std::exception &e) {
|
||||
nhlog::net()->error("Exception while downloading media: {}", e.what());
|
||||
if (!image.isNull()) {
|
||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mtx::http::ThumbOpts opts;
|
||||
opts.mxc_url = "mxc://" + id.toStdString();
|
||||
opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1;
|
||||
opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1;
|
||||
opts.method = crop ? "crop" : "scale";
|
||||
http::client()->get_thumbnail(
|
||||
opts,
|
||||
[fileInfo, requestedSize, radius, then, id](const std::string &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err || res.empty()) {
|
||||
then(id, QSize(), {}, "");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = QByteArray(res.data(), (int)res.size());
|
||||
QImage image = utils::readImage(data);
|
||||
if (!image.isNull()) {
|
||||
image =
|
||||
image.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
}
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
if (image.save(fileInfo.absoluteFilePath(), "png"))
|
||||
nhlog::ui()->debug("Wrote: {}", fileInfo.absoluteFilePath().toStdString());
|
||||
else
|
||||
nhlog::ui()->debug("Failed to write: {}",
|
||||
fileInfo.absoluteFilePath().toStdString());
|
||||
|
||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
QString fileName = QString("%1_radius%2")
|
||||
.arg(QString::fromUtf8(id.toUtf8().toBase64(
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)))
|
||||
.arg(radius);
|
||||
|
||||
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||
"/media_cache",
|
||||
fileName);
|
||||
QDir().mkpath(fileInfo.absolutePath());
|
||||
|
||||
if (fileInfo.exists()) {
|
||||
if (encryptionInfo) {
|
||||
QFile f(fileInfo.absoluteFilePath());
|
||||
f.open(QIODevice::ReadOnly);
|
||||
|
||||
QByteArray fileData = f.readAll();
|
||||
auto tempData = mtx::crypto::to_string(
|
||||
mtx::crypto::decrypt_file(fileData.toStdString(), encryptionInfo.value()));
|
||||
auto data = QByteArray(tempData.data(), (int)tempData.size());
|
||||
QImage image = utils::readImage(data);
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
if (!image.isNull()) {
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath());
|
||||
if (!image.isNull()) {
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http::client()->download(
|
||||
"mxc://" + id.toStdString(),
|
||||
[fileInfo, requestedSize, then, id, radius, encryptionInfo](
|
||||
const std::string &res,
|
||||
const std::string &,
|
||||
const std::string &originalFilename,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
then(id, QSize(), {}, "");
|
||||
return;
|
||||
}
|
||||
|
||||
auto tempData = res;
|
||||
QFile f(fileInfo.absoluteFilePath());
|
||||
if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) {
|
||||
then(id, QSize(), {}, "");
|
||||
return;
|
||||
}
|
||||
f.write(tempData.data(), tempData.size());
|
||||
f.close();
|
||||
|
||||
if (encryptionInfo) {
|
||||
tempData = mtx::crypto::to_string(
|
||||
mtx::crypto::decrypt_file(tempData, encryptionInfo.value()));
|
||||
auto data = QByteArray(tempData.data(), (int)tempData.size());
|
||||
QImage image = utils::readImage(data);
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
image.setText("original filename", QString::fromStdString(originalFilename));
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
return;
|
||||
}
|
||||
|
||||
QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath());
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
image.setText("original filename", QString::fromStdString(originalFilename));
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
});
|
||||
} catch (std::exception &e) {
|
||||
nhlog::net()->error("Exception while downloading media: {}", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,46 +19,46 @@ class MxcImageResponse
|
|||
, public QRunnable
|
||||
{
|
||||
public:
|
||||
MxcImageResponse(const QString &id, bool crop, double radius, const QSize &requestedSize)
|
||||
: m_id(id)
|
||||
, m_requestedSize(requestedSize)
|
||||
, m_crop(crop)
|
||||
, m_radius(radius)
|
||||
{
|
||||
setAutoDelete(false);
|
||||
}
|
||||
MxcImageResponse(const QString &id, bool crop, double radius, const QSize &requestedSize)
|
||||
: m_id(id)
|
||||
, m_requestedSize(requestedSize)
|
||||
, m_crop(crop)
|
||||
, m_radius(radius)
|
||||
{
|
||||
setAutoDelete(false);
|
||||
}
|
||||
|
||||
QQuickTextureFactory *textureFactory() const override
|
||||
{
|
||||
return QQuickTextureFactory::textureFactoryForImage(m_image);
|
||||
}
|
||||
QString errorString() const override { return m_error; }
|
||||
QQuickTextureFactory *textureFactory() const override
|
||||
{
|
||||
return QQuickTextureFactory::textureFactoryForImage(m_image);
|
||||
}
|
||||
QString errorString() const override { return m_error; }
|
||||
|
||||
void run() override;
|
||||
void run() override;
|
||||
|
||||
QString m_id, m_error;
|
||||
QSize m_requestedSize;
|
||||
QImage m_image;
|
||||
bool m_crop;
|
||||
double m_radius;
|
||||
QString m_id, m_error;
|
||||
QSize m_requestedSize;
|
||||
QImage m_image;
|
||||
bool m_crop;
|
||||
double m_radius;
|
||||
};
|
||||
|
||||
class MxcImageProvider
|
||||
: public QObject
|
||||
, public QQuickAsyncImageProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
QQuickImageResponse *requestImageResponse(const QString &id,
|
||||
const QSize &requestedSize) override;
|
||||
QQuickImageResponse *requestImageResponse(const QString &id,
|
||||
const QSize &requestedSize) override;
|
||||
|
||||
static void addEncryptionInfo(mtx::crypto::EncryptedFile info);
|
||||
static void download(const QString &id,
|
||||
const QSize &requestedSize,
|
||||
std::function<void(QString, QSize, QImage, QString)> then,
|
||||
bool crop = true,
|
||||
double radius = 0);
|
||||
static void addEncryptionInfo(mtx::crypto::EncryptedFile info);
|
||||
static void download(const QString &id,
|
||||
const QSize &requestedSize,
|
||||
std::function<void(QString, QSize, QImage, QString)> then,
|
||||
bool crop = true,
|
||||
double radius = 0);
|
||||
|
||||
private:
|
||||
QThreadPool pool;
|
||||
QThreadPool pool;
|
||||
};
|
||||
|
|
|
|||
2710
src/Olm.cpp
2710
src/Olm.cpp
File diff suppressed because it is too large
Load diff
32
src/Olm.h
32
src/Olm.h
|
|
@ -18,32 +18,32 @@ Q_NAMESPACE
|
|||
|
||||
enum DecryptionErrorCode
|
||||
{
|
||||
NoError,
|
||||
MissingSession, // Session was not found, retrieve from backup or request from other devices
|
||||
// and try again
|
||||
MissingSessionIndex, // Session was found, but it does not reach back enough to this index,
|
||||
// retrieve from backup or request from other devices and try again
|
||||
DbError, // DB read failed
|
||||
DecryptionFailed, // libolm error
|
||||
ParsingFailed, // Failed to parse the actual event
|
||||
ReplayAttack, // Megolm index reused
|
||||
NoError,
|
||||
MissingSession, // Session was not found, retrieve from backup or request from other devices
|
||||
// and try again
|
||||
MissingSessionIndex, // Session was found, but it does not reach back enough to this index,
|
||||
// retrieve from backup or request from other devices and try again
|
||||
DbError, // DB read failed
|
||||
DecryptionFailed, // libolm error
|
||||
ParsingFailed, // Failed to parse the actual event
|
||||
ReplayAttack, // Megolm index reused
|
||||
};
|
||||
Q_ENUM_NS(DecryptionErrorCode)
|
||||
|
||||
struct DecryptionResult
|
||||
{
|
||||
DecryptionErrorCode error;
|
||||
std::optional<std::string> error_message;
|
||||
std::optional<mtx::events::collections::TimelineEvents> event;
|
||||
DecryptionErrorCode error;
|
||||
std::optional<std::string> error_message;
|
||||
std::optional<mtx::events::collections::TimelineEvents> event;
|
||||
};
|
||||
|
||||
struct OlmMessage
|
||||
{
|
||||
std::string sender_key;
|
||||
std::string sender;
|
||||
std::string sender_key;
|
||||
std::string sender;
|
||||
|
||||
using RecipientKey = std::string;
|
||||
std::map<RecipientKey, mtx::events::msg::OlmCipherContent> ciphertext;
|
||||
using RecipientKey = std::string;
|
||||
std::map<RecipientKey, mtx::events::msg::OlmCipherContent> ciphertext;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -16,116 +16,115 @@ ReadReceiptsModel::ReadReceiptsModel(QString event_id, QString room_id, QObject
|
|||
, event_id_{event_id}
|
||||
, room_id_{room_id}
|
||||
{
|
||||
try {
|
||||
addUsers(cache::readReceipts(event_id_, room_id_));
|
||||
} catch (const lmdb::error &) {
|
||||
nhlog::db()->warn("failed to retrieve read receipts for {} {}",
|
||||
event_id_.toStdString(),
|
||||
room_id_.toStdString());
|
||||
try {
|
||||
addUsers(cache::readReceipts(event_id_, room_id_));
|
||||
} catch (const lmdb::error &) {
|
||||
nhlog::db()->warn("failed to retrieve read receipts for {} {}",
|
||||
event_id_.toStdString(),
|
||||
room_id_.toStdString());
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
connect(cache::client(), &Cache::newReadReceipts, this, &ReadReceiptsModel::update);
|
||||
connect(cache::client(), &Cache::newReadReceipts, this, &ReadReceiptsModel::update);
|
||||
}
|
||||
|
||||
void
|
||||
ReadReceiptsModel::update()
|
||||
{
|
||||
try {
|
||||
addUsers(cache::readReceipts(event_id_, room_id_));
|
||||
} catch (const lmdb::error &) {
|
||||
nhlog::db()->warn("failed to retrieve read receipts for {} {}",
|
||||
event_id_.toStdString(),
|
||||
room_id_.toStdString());
|
||||
try {
|
||||
addUsers(cache::readReceipts(event_id_, room_id_));
|
||||
} catch (const lmdb::error &) {
|
||||
nhlog::db()->warn("failed to retrieve read receipts for {} {}",
|
||||
event_id_.toStdString(),
|
||||
room_id_.toStdString());
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
ReadReceiptsModel::roleNames() const
|
||||
{
|
||||
// Note: RawTimestamp is purposely not included here
|
||||
return {
|
||||
{Mxid, "mxid"},
|
||||
{DisplayName, "displayName"},
|
||||
{AvatarUrl, "avatarUrl"},
|
||||
{Timestamp, "timestamp"},
|
||||
};
|
||||
// Note: RawTimestamp is purposely not included here
|
||||
return {
|
||||
{Mxid, "mxid"},
|
||||
{DisplayName, "displayName"},
|
||||
{AvatarUrl, "avatarUrl"},
|
||||
{Timestamp, "timestamp"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
ReadReceiptsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= (int)readReceipts_.size() || index.row() < 0)
|
||||
return {};
|
||||
if (!index.isValid() || index.row() >= (int)readReceipts_.size() || index.row() < 0)
|
||||
return {};
|
||||
|
||||
switch (role) {
|
||||
case Mxid:
|
||||
return readReceipts_[index.row()].first;
|
||||
case DisplayName:
|
||||
return cache::displayName(room_id_, readReceipts_[index.row()].first);
|
||||
case AvatarUrl:
|
||||
return cache::avatarUrl(room_id_, readReceipts_[index.row()].first);
|
||||
case Timestamp:
|
||||
return dateFormat(readReceipts_[index.row()].second);
|
||||
case RawTimestamp:
|
||||
return readReceipts_[index.row()].second;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
switch (role) {
|
||||
case Mxid:
|
||||
return readReceipts_[index.row()].first;
|
||||
case DisplayName:
|
||||
return cache::displayName(room_id_, readReceipts_[index.row()].first);
|
||||
case AvatarUrl:
|
||||
return cache::avatarUrl(room_id_, readReceipts_[index.row()].first);
|
||||
case Timestamp:
|
||||
return dateFormat(readReceipts_[index.row()].second);
|
||||
case RawTimestamp:
|
||||
return readReceipts_[index.row()].second;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ReadReceiptsModel::addUsers(
|
||||
const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &users)
|
||||
{
|
||||
auto newReceipts = users.size() - readReceipts_.size();
|
||||
auto newReceipts = users.size() - readReceipts_.size();
|
||||
|
||||
if (newReceipts > 0) {
|
||||
beginInsertRows(
|
||||
QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1);
|
||||
if (newReceipts > 0) {
|
||||
beginInsertRows(
|
||||
QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1);
|
||||
|
||||
for (const auto &user : users) {
|
||||
QPair<QString, QDateTime> item = {
|
||||
QString::fromStdString(user.second),
|
||||
QDateTime::fromMSecsSinceEpoch(user.first)};
|
||||
if (!readReceipts_.contains(item))
|
||||
readReceipts_.push_back(item);
|
||||
}
|
||||
|
||||
endInsertRows();
|
||||
for (const auto &user : users) {
|
||||
QPair<QString, QDateTime> item = {QString::fromStdString(user.second),
|
||||
QDateTime::fromMSecsSinceEpoch(user.first)};
|
||||
if (!readReceipts_.contains(item))
|
||||
readReceipts_.push_back(item);
|
||||
}
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
QString
|
||||
ReadReceiptsModel::dateFormat(const QDateTime &then) const
|
||||
{
|
||||
auto now = QDateTime::currentDateTime();
|
||||
auto days = then.daysTo(now);
|
||||
|
||||
if (days == 0)
|
||||
return QLocale::system().toString(then.time(), QLocale::ShortFormat);
|
||||
else if (days < 2)
|
||||
return tr("Yesterday, %1")
|
||||
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
|
||||
else if (days < 7)
|
||||
//: %1 is the name of the current day, %2 is the time the read receipt was read. The
|
||||
//: result may look like this: Monday, 7:15
|
||||
return QString("%1, %2")
|
||||
.arg(then.toString("dddd"))
|
||||
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
|
||||
auto now = QDateTime::currentDateTime();
|
||||
auto days = then.daysTo(now);
|
||||
|
||||
if (days == 0)
|
||||
return QLocale::system().toString(then.time(), QLocale::ShortFormat);
|
||||
else if (days < 2)
|
||||
return tr("Yesterday, %1")
|
||||
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
|
||||
else if (days < 7)
|
||||
//: %1 is the name of the current day, %2 is the time the read receipt was read. The
|
||||
//: result may look like this: Monday, 7:15
|
||||
return QString("%1, %2")
|
||||
.arg(then.toString("dddd"))
|
||||
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
|
||||
|
||||
return QLocale::system().toString(then.time(), QLocale::ShortFormat);
|
||||
}
|
||||
|
||||
ReadReceiptsProxy::ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent)
|
||||
: QSortFilterProxyModel{parent}
|
||||
, model_{event_id, room_id, this}
|
||||
{
|
||||
setSourceModel(&model_);
|
||||
setSortRole(ReadReceiptsModel::RawTimestamp);
|
||||
sort(0, Qt::DescendingOrder);
|
||||
setDynamicSortFilter(true);
|
||||
setSourceModel(&model_);
|
||||
setSortRole(ReadReceiptsModel::RawTimestamp);
|
||||
sort(0, Qt::DescendingOrder);
|
||||
setDynamicSortFilter(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,61 +13,61 @@
|
|||
|
||||
class ReadReceiptsModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
Mxid,
|
||||
DisplayName,
|
||||
AvatarUrl,
|
||||
Timestamp,
|
||||
RawTimestamp,
|
||||
};
|
||||
enum Roles
|
||||
{
|
||||
Mxid,
|
||||
DisplayName,
|
||||
AvatarUrl,
|
||||
Timestamp,
|
||||
RawTimestamp,
|
||||
};
|
||||
|
||||
explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr);
|
||||
explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr);
|
||||
|
||||
QString eventId() const { return event_id_; }
|
||||
QString roomId() const { return room_id_; }
|
||||
QString eventId() const { return event_id_; }
|
||||
QString roomId() const { return room_id_; }
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent) const override
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return readReceipts_.size();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent) const override
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return readReceipts_.size();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
public slots:
|
||||
void addUsers(const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &users);
|
||||
void update();
|
||||
void addUsers(const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &users);
|
||||
void update();
|
||||
|
||||
private:
|
||||
QString dateFormat(const QDateTime &then) const;
|
||||
QString dateFormat(const QDateTime &then) const;
|
||||
|
||||
QString event_id_;
|
||||
QString room_id_;
|
||||
QVector<QPair<QString, QDateTime>> readReceipts_;
|
||||
QString event_id_;
|
||||
QString room_id_;
|
||||
QVector<QPair<QString, QDateTime>> readReceipts_;
|
||||
};
|
||||
|
||||
class ReadReceiptsProxy : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString eventId READ eventId CONSTANT)
|
||||
Q_PROPERTY(QString roomId READ roomId CONSTANT)
|
||||
Q_PROPERTY(QString eventId READ eventId CONSTANT)
|
||||
Q_PROPERTY(QString roomId READ roomId CONSTANT)
|
||||
|
||||
public:
|
||||
explicit ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent = nullptr);
|
||||
explicit ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent = nullptr);
|
||||
|
||||
QString eventId() const { return event_id_; }
|
||||
QString roomId() const { return room_id_; }
|
||||
QString eventId() const { return event_id_; }
|
||||
QString roomId() const { return room_id_; }
|
||||
|
||||
private:
|
||||
QString event_id_;
|
||||
QString room_id_;
|
||||
QString event_id_;
|
||||
QString room_id_;
|
||||
|
||||
ReadReceiptsModel model_;
|
||||
ReadReceiptsModel model_;
|
||||
};
|
||||
|
||||
#endif // READRECEIPTSMODEL_H
|
||||
|
|
|
|||
|
|
@ -33,496 +33,483 @@ Q_DECLARE_METATYPE(mtx::user_interactive::Auth)
|
|||
RegisterPage::RegisterPage(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
qRegisterMetaType<mtx::user_interactive::Unauthorized>();
|
||||
qRegisterMetaType<mtx::user_interactive::Auth>();
|
||||
top_layout_ = new QVBoxLayout();
|
||||
qRegisterMetaType<mtx::user_interactive::Unauthorized>();
|
||||
qRegisterMetaType<mtx::user_interactive::Auth>();
|
||||
top_layout_ = new QVBoxLayout();
|
||||
|
||||
back_layout_ = new QHBoxLayout();
|
||||
back_layout_->setSpacing(0);
|
||||
back_layout_->setContentsMargins(5, 5, -1, -1);
|
||||
back_layout_ = new QHBoxLayout();
|
||||
back_layout_->setSpacing(0);
|
||||
back_layout_->setContentsMargins(5, 5, -1, -1);
|
||||
|
||||
back_button_ = new FlatButton(this);
|
||||
back_button_->setMinimumSize(QSize(30, 30));
|
||||
back_button_ = new FlatButton(this);
|
||||
back_button_->setMinimumSize(QSize(30, 30));
|
||||
|
||||
QIcon icon;
|
||||
icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png");
|
||||
QIcon icon;
|
||||
icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png");
|
||||
|
||||
back_button_->setIcon(icon);
|
||||
back_button_->setIconSize(QSize(32, 32));
|
||||
back_button_->setIcon(icon);
|
||||
back_button_->setIconSize(QSize(32, 32));
|
||||
|
||||
back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
back_layout_->addStretch(1);
|
||||
back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
back_layout_->addStretch(1);
|
||||
|
||||
QIcon logo;
|
||||
logo.addFile(":/logos/register.png");
|
||||
QIcon logo;
|
||||
logo.addFile(":/logos/register.png");
|
||||
|
||||
logo_ = new QLabel(this);
|
||||
logo_->setPixmap(logo.pixmap(128));
|
||||
logo_ = new QLabel(this);
|
||||
logo_->setPixmap(logo.pixmap(128));
|
||||
|
||||
logo_layout_ = new QHBoxLayout();
|
||||
logo_layout_->setMargin(0);
|
||||
logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
|
||||
logo_layout_ = new QHBoxLayout();
|
||||
logo_layout_->setMargin(0);
|
||||
logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
|
||||
|
||||
form_wrapper_ = new QHBoxLayout();
|
||||
form_widget_ = new QWidget();
|
||||
form_widget_->setMinimumSize(QSize(350, 300));
|
||||
form_wrapper_ = new QHBoxLayout();
|
||||
form_widget_ = new QWidget();
|
||||
form_widget_->setMinimumSize(QSize(350, 300));
|
||||
|
||||
form_layout_ = new QVBoxLayout();
|
||||
form_layout_->setSpacing(20);
|
||||
form_layout_->setContentsMargins(0, 0, 0, 40);
|
||||
form_widget_->setLayout(form_layout_);
|
||||
form_layout_ = new QVBoxLayout();
|
||||
form_layout_->setSpacing(20);
|
||||
form_layout_->setContentsMargins(0, 0, 0, 40);
|
||||
form_widget_->setLayout(form_layout_);
|
||||
|
||||
form_wrapper_->addStretch(1);
|
||||
form_wrapper_->addWidget(form_widget_);
|
||||
form_wrapper_->addStretch(1);
|
||||
form_wrapper_->addStretch(1);
|
||||
form_wrapper_->addWidget(form_widget_);
|
||||
form_wrapper_->addStretch(1);
|
||||
|
||||
username_input_ = new TextField();
|
||||
username_input_->setLabel(tr("Username"));
|
||||
username_input_->setRegexp(QRegularExpression("[a-z0-9._=/-]+"));
|
||||
username_input_->setToolTip(tr("The username must not be empty, and must contain only the "
|
||||
"characters a-z, 0-9, ., _, =, -, and /."));
|
||||
username_input_ = new TextField();
|
||||
username_input_->setLabel(tr("Username"));
|
||||
username_input_->setRegexp(QRegularExpression("[a-z0-9._=/-]+"));
|
||||
username_input_->setToolTip(tr("The username must not be empty, and must contain only the "
|
||||
"characters a-z, 0-9, ., _, =, -, and /."));
|
||||
|
||||
password_input_ = new TextField();
|
||||
password_input_->setLabel(tr("Password"));
|
||||
password_input_->setRegexp(QRegularExpression("^.{8,}$"));
|
||||
password_input_->setEchoMode(QLineEdit::Password);
|
||||
password_input_->setToolTip(tr("Please choose a secure password. The exact requirements "
|
||||
"for password strength may depend on your server."));
|
||||
password_input_ = new TextField();
|
||||
password_input_->setLabel(tr("Password"));
|
||||
password_input_->setRegexp(QRegularExpression("^.{8,}$"));
|
||||
password_input_->setEchoMode(QLineEdit::Password);
|
||||
password_input_->setToolTip(tr("Please choose a secure password. The exact requirements "
|
||||
"for password strength may depend on your server."));
|
||||
|
||||
password_confirmation_ = new TextField();
|
||||
password_confirmation_->setLabel(tr("Password confirmation"));
|
||||
password_confirmation_->setEchoMode(QLineEdit::Password);
|
||||
password_confirmation_ = new TextField();
|
||||
password_confirmation_->setLabel(tr("Password confirmation"));
|
||||
password_confirmation_->setEchoMode(QLineEdit::Password);
|
||||
|
||||
server_input_ = new TextField();
|
||||
server_input_->setLabel(tr("Homeserver"));
|
||||
server_input_->setRegexp(QRegularExpression(".+"));
|
||||
server_input_->setToolTip(
|
||||
tr("A server that allows registration. Since matrix is decentralized, you need to first "
|
||||
"find a server you can register on or host your own."));
|
||||
server_input_ = new TextField();
|
||||
server_input_->setLabel(tr("Homeserver"));
|
||||
server_input_->setRegexp(QRegularExpression(".+"));
|
||||
server_input_->setToolTip(
|
||||
tr("A server that allows registration. Since matrix is decentralized, you need to first "
|
||||
"find a server you can register on or host your own."));
|
||||
|
||||
error_username_label_ = new QLabel(this);
|
||||
error_username_label_->setWordWrap(true);
|
||||
error_username_label_->hide();
|
||||
error_username_label_ = new QLabel(this);
|
||||
error_username_label_->setWordWrap(true);
|
||||
error_username_label_->hide();
|
||||
|
||||
error_password_label_ = new QLabel(this);
|
||||
error_password_label_->setWordWrap(true);
|
||||
error_password_label_->hide();
|
||||
error_password_label_ = new QLabel(this);
|
||||
error_password_label_->setWordWrap(true);
|
||||
error_password_label_->hide();
|
||||
|
||||
error_password_confirmation_label_ = new QLabel(this);
|
||||
error_password_confirmation_label_->setWordWrap(true);
|
||||
error_password_confirmation_label_->hide();
|
||||
error_password_confirmation_label_ = new QLabel(this);
|
||||
error_password_confirmation_label_->setWordWrap(true);
|
||||
error_password_confirmation_label_->hide();
|
||||
|
||||
error_server_label_ = new QLabel(this);
|
||||
error_server_label_->setWordWrap(true);
|
||||
error_server_label_->hide();
|
||||
error_server_label_ = new QLabel(this);
|
||||
error_server_label_->setWordWrap(true);
|
||||
error_server_label_->hide();
|
||||
|
||||
form_layout_->addWidget(username_input_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_username_label_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(password_input_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_password_label_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(server_input_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_server_label_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(username_input_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_username_label_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(password_input_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_password_label_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(server_input_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_server_label_, Qt::AlignHCenter);
|
||||
|
||||
button_layout_ = new QHBoxLayout();
|
||||
button_layout_->setSpacing(0);
|
||||
button_layout_->setMargin(0);
|
||||
button_layout_ = new QHBoxLayout();
|
||||
button_layout_->setSpacing(0);
|
||||
button_layout_->setMargin(0);
|
||||
|
||||
error_label_ = new QLabel(this);
|
||||
error_label_->setWordWrap(true);
|
||||
error_label_ = new QLabel(this);
|
||||
error_label_->setWordWrap(true);
|
||||
|
||||
register_button_ = new RaisedButton(tr("REGISTER"), this);
|
||||
register_button_->setMinimumSize(350, 65);
|
||||
register_button_->setFontSize(conf::btn::fontSize);
|
||||
register_button_->setCornerRadius(conf::btn::cornerRadius);
|
||||
register_button_ = new RaisedButton(tr("REGISTER"), this);
|
||||
register_button_->setMinimumSize(350, 65);
|
||||
register_button_->setFontSize(conf::btn::fontSize);
|
||||
register_button_->setCornerRadius(conf::btn::cornerRadius);
|
||||
|
||||
button_layout_->addStretch(1);
|
||||
button_layout_->addWidget(register_button_);
|
||||
button_layout_->addStretch(1);
|
||||
button_layout_->addStretch(1);
|
||||
button_layout_->addWidget(register_button_);
|
||||
button_layout_->addStretch(1);
|
||||
|
||||
top_layout_->addLayout(back_layout_);
|
||||
top_layout_->addLayout(logo_layout_);
|
||||
top_layout_->addLayout(form_wrapper_);
|
||||
top_layout_->addStretch(1);
|
||||
top_layout_->addLayout(button_layout_);
|
||||
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
|
||||
top_layout_->addStretch(1);
|
||||
setLayout(top_layout_);
|
||||
top_layout_->addLayout(back_layout_);
|
||||
top_layout_->addLayout(logo_layout_);
|
||||
top_layout_->addLayout(form_wrapper_);
|
||||
top_layout_->addStretch(1);
|
||||
top_layout_->addLayout(button_layout_);
|
||||
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
|
||||
top_layout_->addStretch(1);
|
||||
setLayout(top_layout_);
|
||||
|
||||
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
|
||||
connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked()));
|
||||
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
|
||||
connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked()));
|
||||
|
||||
connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername);
|
||||
connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword);
|
||||
connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(password_confirmation_,
|
||||
&TextField::editingFinished,
|
||||
this,
|
||||
&RegisterPage::checkPasswordConfirmation);
|
||||
connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer);
|
||||
connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername);
|
||||
connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword);
|
||||
connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(password_confirmation_,
|
||||
&TextField::editingFinished,
|
||||
this,
|
||||
&RegisterPage::checkPasswordConfirmation);
|
||||
connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer);
|
||||
|
||||
connect(
|
||||
this,
|
||||
&RegisterPage::serverError,
|
||||
this,
|
||||
[this](const QString &msg) {
|
||||
server_input_->setValid(false);
|
||||
showError(error_server_label_, msg);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
connect(
|
||||
this,
|
||||
&RegisterPage::serverError,
|
||||
this,
|
||||
[this](const QString &msg) {
|
||||
server_input_->setValid(false);
|
||||
showError(error_server_label_, msg);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup);
|
||||
connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck);
|
||||
connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration);
|
||||
connect(this, &RegisterPage::UIA, this, &RegisterPage::doUIA);
|
||||
connect(
|
||||
this, &RegisterPage::registrationWithAuth, this, &RegisterPage::doRegistrationWithAuth);
|
||||
connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup);
|
||||
connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck);
|
||||
connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration);
|
||||
connect(this, &RegisterPage::UIA, this, &RegisterPage::doUIA);
|
||||
connect(this, &RegisterPage::registrationWithAuth, this, &RegisterPage::doRegistrationWithAuth);
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::onBackButtonClicked()
|
||||
{
|
||||
emit backButtonClicked();
|
||||
emit backButtonClicked();
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::showError(const QString &msg)
|
||||
{
|
||||
emit errorOccurred();
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
error_label_->setFixedHeight(qCeil(width / 200.0) * height);
|
||||
error_label_->setText(msg);
|
||||
emit errorOccurred();
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
error_label_->setFixedHeight(qCeil(width / 200.0) * height);
|
||||
error_label_->setText(msg);
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::showError(QLabel *label, const QString &msg)
|
||||
{
|
||||
emit errorOccurred();
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
label->setFixedHeight((int)qCeil(width / 200.0) * height);
|
||||
label->setText(msg);
|
||||
label->show();
|
||||
emit errorOccurred();
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
label->setFixedHeight((int)qCeil(width / 200.0) * height);
|
||||
label->setText(msg);
|
||||
label->show();
|
||||
}
|
||||
|
||||
bool
|
||||
RegisterPage::checkOneField(QLabel *label, const TextField *t_field, const QString &msg)
|
||||
{
|
||||
if (t_field->isValid()) {
|
||||
label->hide();
|
||||
return true;
|
||||
} else {
|
||||
showError(label, msg);
|
||||
return false;
|
||||
}
|
||||
if (t_field->isValid()) {
|
||||
label->hide();
|
||||
return true;
|
||||
} else {
|
||||
showError(label, msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
RegisterPage::checkUsername()
|
||||
{
|
||||
return checkOneField(error_username_label_,
|
||||
username_input_,
|
||||
tr("The username must not be empty, and must contain only the "
|
||||
"characters a-z, 0-9, ., _, =, -, and /."));
|
||||
return checkOneField(error_username_label_,
|
||||
username_input_,
|
||||
tr("The username must not be empty, and must contain only the "
|
||||
"characters a-z, 0-9, ., _, =, -, and /."));
|
||||
}
|
||||
|
||||
bool
|
||||
RegisterPage::checkPassword()
|
||||
{
|
||||
return checkOneField(
|
||||
error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)"));
|
||||
return checkOneField(
|
||||
error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)"));
|
||||
}
|
||||
|
||||
bool
|
||||
RegisterPage::checkPasswordConfirmation()
|
||||
{
|
||||
if (password_input_->text() == password_confirmation_->text()) {
|
||||
error_password_confirmation_label_->hide();
|
||||
password_confirmation_->setValid(true);
|
||||
return true;
|
||||
} else {
|
||||
showError(error_password_confirmation_label_, tr("Passwords don't match"));
|
||||
password_confirmation_->setValid(false);
|
||||
return false;
|
||||
}
|
||||
if (password_input_->text() == password_confirmation_->text()) {
|
||||
error_password_confirmation_label_->hide();
|
||||
password_confirmation_->setValid(true);
|
||||
return true;
|
||||
} else {
|
||||
showError(error_password_confirmation_label_, tr("Passwords don't match"));
|
||||
password_confirmation_->setValid(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
RegisterPage::checkServer()
|
||||
{
|
||||
// This doesn't check that the server is reachable,
|
||||
// just that the input is not obviously wrong.
|
||||
return checkOneField(error_server_label_, server_input_, tr("Invalid server name"));
|
||||
// This doesn't check that the server is reachable,
|
||||
// just that the input is not obviously wrong.
|
||||
return checkOneField(error_server_label_, server_input_, tr("Invalid server name"));
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::onRegisterButtonClicked()
|
||||
{
|
||||
if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) {
|
||||
auto server = server_input_->text().toStdString();
|
||||
if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) {
|
||||
auto server = server_input_->text().toStdString();
|
||||
|
||||
http::client()->set_server(server);
|
||||
http::client()->verify_certificates(
|
||||
!UserSettings::instance()->disableCertificateValidation());
|
||||
http::client()->set_server(server);
|
||||
http::client()->verify_certificates(
|
||||
!UserSettings::instance()->disableCertificateValidation());
|
||||
|
||||
// This starts a chain of `emit`s which ends up doing the
|
||||
// registration. Signals are used rather than normal function
|
||||
// calls so that the dialogs used in UIA work correctly.
|
||||
//
|
||||
// The sequence of events looks something like this:
|
||||
//
|
||||
// dowellKnownLookup
|
||||
// v
|
||||
// doVersionsCheck
|
||||
// v
|
||||
// doRegistration
|
||||
// v
|
||||
// doUIA <-----------------+
|
||||
// v | More auth required
|
||||
// doRegistrationWithAuth -+
|
||||
// | Success
|
||||
// v
|
||||
// registering
|
||||
// This starts a chain of `emit`s which ends up doing the
|
||||
// registration. Signals are used rather than normal function
|
||||
// calls so that the dialogs used in UIA work correctly.
|
||||
//
|
||||
// The sequence of events looks something like this:
|
||||
//
|
||||
// dowellKnownLookup
|
||||
// v
|
||||
// doVersionsCheck
|
||||
// v
|
||||
// doRegistration
|
||||
// v
|
||||
// doUIA <-----------------+
|
||||
// v | More auth required
|
||||
// doRegistrationWithAuth -+
|
||||
// | Success
|
||||
// v
|
||||
// registering
|
||||
|
||||
emit wellKnownLookup();
|
||||
emit wellKnownLookup();
|
||||
|
||||
emit registering();
|
||||
}
|
||||
emit registering();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::doWellKnownLookup()
|
||||
{
|
||||
http::client()->well_known(
|
||||
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
if (err->status_code == 404) {
|
||||
nhlog::net()->info("Autodiscovery: No .well-known.");
|
||||
// Check that the homeserver can be reached
|
||||
emit versionsCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!err->parse_error.empty()) {
|
||||
emit serverError(
|
||||
tr("Autodiscovery failed. Received malformed response."));
|
||||
nhlog::net()->error(
|
||||
"Autodiscovery failed. Received malformed response.");
|
||||
return;
|
||||
}
|
||||
|
||||
emit serverError(tr("Autodiscovery failed. Unknown error when "
|
||||
"requesting .well-known."));
|
||||
nhlog::net()->error("Autodiscovery failed. Unknown error when "
|
||||
"requesting .well-known. {} {}",
|
||||
err->status_code,
|
||||
err->error_code);
|
||||
return;
|
||||
}
|
||||
|
||||
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
|
||||
http::client()->set_server(res.homeserver.base_url);
|
||||
http::client()->well_known(
|
||||
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
if (err->status_code == 404) {
|
||||
nhlog::net()->info("Autodiscovery: No .well-known.");
|
||||
// Check that the homeserver can be reached
|
||||
emit versionsCheck();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!err->parse_error.empty()) {
|
||||
emit serverError(tr("Autodiscovery failed. Received malformed response."));
|
||||
nhlog::net()->error("Autodiscovery failed. Received malformed response.");
|
||||
return;
|
||||
}
|
||||
|
||||
emit serverError(tr("Autodiscovery failed. Unknown error when "
|
||||
"requesting .well-known."));
|
||||
nhlog::net()->error("Autodiscovery failed. Unknown error when "
|
||||
"requesting .well-known. {} {}",
|
||||
err->status_code,
|
||||
err->error_code);
|
||||
return;
|
||||
}
|
||||
|
||||
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
|
||||
http::client()->set_server(res.homeserver.base_url);
|
||||
// Check that the homeserver can be reached
|
||||
emit versionsCheck();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::doVersionsCheck()
|
||||
{
|
||||
// Make a request to /_matrix/client/versions to check the address
|
||||
// given is a Matrix homeserver.
|
||||
http::client()->versions(
|
||||
[this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
if (err->status_code == 404) {
|
||||
emit serverError(
|
||||
tr("The required endpoints were not found. Possibly "
|
||||
"not a Matrix server."));
|
||||
return;
|
||||
}
|
||||
// Make a request to /_matrix/client/versions to check the address
|
||||
// given is a Matrix homeserver.
|
||||
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
if (err->status_code == 404) {
|
||||
emit serverError(tr("The required endpoints were not found. Possibly "
|
||||
"not a Matrix server."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!err->parse_error.empty()) {
|
||||
emit serverError(
|
||||
tr("Received malformed response. Make sure the homeserver "
|
||||
"domain is valid."));
|
||||
return;
|
||||
}
|
||||
if (!err->parse_error.empty()) {
|
||||
emit serverError(tr("Received malformed response. Make sure the homeserver "
|
||||
"domain is valid."));
|
||||
return;
|
||||
}
|
||||
|
||||
emit serverError(tr("An unknown error occured. Make sure the "
|
||||
"homeserver domain is valid."));
|
||||
return;
|
||||
}
|
||||
emit serverError(tr("An unknown error occured. Make sure the "
|
||||
"homeserver domain is valid."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt registration without an `auth` dict
|
||||
emit registration();
|
||||
});
|
||||
// Attempt registration without an `auth` dict
|
||||
emit registration();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::doRegistration()
|
||||
{
|
||||
// These inputs should still be alright, but check just in case
|
||||
if (checkUsername() && checkPassword() && checkPasswordConfirmation()) {
|
||||
auto username = username_input_->text().toStdString();
|
||||
auto password = password_input_->text().toStdString();
|
||||
http::client()->registration(username, password, registrationCb());
|
||||
}
|
||||
// These inputs should still be alright, but check just in case
|
||||
if (checkUsername() && checkPassword() && checkPasswordConfirmation()) {
|
||||
auto username = username_input_->text().toStdString();
|
||||
auto password = password_input_->text().toStdString();
|
||||
http::client()->registration(username, password, registrationCb());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::doRegistrationWithAuth(const mtx::user_interactive::Auth &auth)
|
||||
{
|
||||
// These inputs should still be alright, but check just in case
|
||||
if (checkUsername() && checkPassword() && checkPasswordConfirmation()) {
|
||||
auto username = username_input_->text().toStdString();
|
||||
auto password = password_input_->text().toStdString();
|
||||
http::client()->registration(username, password, auth, registrationCb());
|
||||
}
|
||||
// These inputs should still be alright, but check just in case
|
||||
if (checkUsername() && checkPassword() && checkPasswordConfirmation()) {
|
||||
auto username = username_input_->text().toStdString();
|
||||
auto password = password_input_->text().toStdString();
|
||||
http::client()->registration(username, password, auth, registrationCb());
|
||||
}
|
||||
}
|
||||
|
||||
mtx::http::Callback<mtx::responses::Register>
|
||||
RegisterPage::registrationCb()
|
||||
{
|
||||
// Return a function to be used as the callback when an attempt at
|
||||
// registration is made.
|
||||
return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) {
|
||||
if (!err) {
|
||||
http::client()->set_user(res.user_id);
|
||||
http::client()->set_access_token(res.access_token);
|
||||
emit registerOk();
|
||||
return;
|
||||
}
|
||||
|
||||
// The server requires registration flows.
|
||||
if (err->status_code == 401) {
|
||||
if (err->matrix_error.unauthorized.flows.empty()) {
|
||||
nhlog::net()->warn("failed to retrieve registration flows: "
|
||||
"status_code({}), matrix_error({}) ",
|
||||
static_cast<int>(err->status_code),
|
||||
err->matrix_error.error);
|
||||
showError(QString::fromStdString(err->matrix_error.error));
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to complete a UIA stage
|
||||
emit UIA(err->matrix_error.unauthorized);
|
||||
return;
|
||||
}
|
||||
|
||||
nhlog::net()->error("failed to register: status_code ({}), matrix_error({})",
|
||||
static_cast<int>(err->status_code),
|
||||
err->matrix_error.error);
|
||||
// Return a function to be used as the callback when an attempt at
|
||||
// registration is made.
|
||||
return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) {
|
||||
if (!err) {
|
||||
http::client()->set_user(res.user_id);
|
||||
http::client()->set_access_token(res.access_token);
|
||||
emit registerOk();
|
||||
return;
|
||||
}
|
||||
|
||||
// The server requires registration flows.
|
||||
if (err->status_code == 401) {
|
||||
if (err->matrix_error.unauthorized.flows.empty()) {
|
||||
nhlog::net()->warn("failed to retrieve registration flows: "
|
||||
"status_code({}), matrix_error({}) ",
|
||||
static_cast<int>(err->status_code),
|
||||
err->matrix_error.error);
|
||||
showError(QString::fromStdString(err->matrix_error.error));
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to complete a UIA stage
|
||||
emit UIA(err->matrix_error.unauthorized);
|
||||
return;
|
||||
}
|
||||
|
||||
nhlog::net()->error("failed to register: status_code ({}), matrix_error({})",
|
||||
static_cast<int>(err->status_code),
|
||||
err->matrix_error.error);
|
||||
|
||||
showError(QString::fromStdString(err->matrix_error.error));
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized)
|
||||
{
|
||||
auto completed_stages = unauthorized.completed;
|
||||
auto flows = unauthorized.flows;
|
||||
auto session =
|
||||
unauthorized.session.empty() ? http::client()->generate_txn_id() : unauthorized.session;
|
||||
auto completed_stages = unauthorized.completed;
|
||||
auto flows = unauthorized.flows;
|
||||
auto session =
|
||||
unauthorized.session.empty() ? http::client()->generate_txn_id() : unauthorized.session;
|
||||
|
||||
nhlog::ui()->info("Completed stages: {}", completed_stages.size());
|
||||
nhlog::ui()->info("Completed stages: {}", completed_stages.size());
|
||||
|
||||
if (!completed_stages.empty()) {
|
||||
// Get rid of all flows which don't start with the sequence of
|
||||
// stages that have already been completed.
|
||||
flows.erase(
|
||||
std::remove_if(flows.begin(),
|
||||
flows.end(),
|
||||
[completed_stages](auto flow) {
|
||||
if (completed_stages.size() > flow.stages.size())
|
||||
return true;
|
||||
for (size_t f = 0; f < completed_stages.size(); f++)
|
||||
if (completed_stages[f] != flow.stages[f])
|
||||
return true;
|
||||
return false;
|
||||
}),
|
||||
flows.end());
|
||||
}
|
||||
if (!completed_stages.empty()) {
|
||||
// Get rid of all flows which don't start with the sequence of
|
||||
// stages that have already been completed.
|
||||
flows.erase(std::remove_if(flows.begin(),
|
||||
flows.end(),
|
||||
[completed_stages](auto flow) {
|
||||
if (completed_stages.size() > flow.stages.size())
|
||||
return true;
|
||||
for (size_t f = 0; f < completed_stages.size(); f++)
|
||||
if (completed_stages[f] != flow.stages[f])
|
||||
return true;
|
||||
return false;
|
||||
}),
|
||||
flows.end());
|
||||
}
|
||||
|
||||
if (flows.empty()) {
|
||||
nhlog::ui()->error("No available registration flows!");
|
||||
showError(tr("No supported registration flows!"));
|
||||
return;
|
||||
}
|
||||
if (flows.empty()) {
|
||||
nhlog::ui()->error("No available registration flows!");
|
||||
showError(tr("No supported registration flows!"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto current_stage = flows.front().stages.at(completed_stages.size());
|
||||
auto current_stage = flows.front().stages.at(completed_stages.size());
|
||||
|
||||
if (current_stage == mtx::user_interactive::auth_types::recaptcha) {
|
||||
auto captchaDialog = new dialogs::ReCaptcha(QString::fromStdString(session), this);
|
||||
if (current_stage == mtx::user_interactive::auth_types::recaptcha) {
|
||||
auto captchaDialog = new dialogs::ReCaptcha(QString::fromStdString(session), this);
|
||||
|
||||
connect(captchaDialog,
|
||||
&dialogs::ReCaptcha::confirmation,
|
||||
this,
|
||||
[this, session, captchaDialog]() {
|
||||
captchaDialog->close();
|
||||
captchaDialog->deleteLater();
|
||||
doRegistrationWithAuth(mtx::user_interactive::Auth{
|
||||
session, mtx::user_interactive::auth::Fallback{}});
|
||||
});
|
||||
connect(
|
||||
captchaDialog, &dialogs::ReCaptcha::confirmation, this, [this, session, captchaDialog]() {
|
||||
captchaDialog->close();
|
||||
captchaDialog->deleteLater();
|
||||
doRegistrationWithAuth(
|
||||
mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Fallback{}});
|
||||
});
|
||||
|
||||
connect(
|
||||
captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred);
|
||||
connect(captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred);
|
||||
|
||||
QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); });
|
||||
QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); });
|
||||
|
||||
} else if (current_stage == mtx::user_interactive::auth_types::dummy) {
|
||||
doRegistrationWithAuth(
|
||||
mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}});
|
||||
} else if (current_stage == mtx::user_interactive::auth_types::dummy) {
|
||||
doRegistrationWithAuth(
|
||||
mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}});
|
||||
|
||||
} else if (current_stage == mtx::user_interactive::auth_types::registration_token) {
|
||||
bool ok;
|
||||
QString token =
|
||||
QInputDialog::getText(this,
|
||||
tr("Registration token"),
|
||||
tr("Please enter a valid registration token."),
|
||||
QLineEdit::Normal,
|
||||
QString(),
|
||||
&ok);
|
||||
} else if (current_stage == mtx::user_interactive::auth_types::registration_token) {
|
||||
bool ok;
|
||||
QString token = QInputDialog::getText(this,
|
||||
tr("Registration token"),
|
||||
tr("Please enter a valid registration token."),
|
||||
QLineEdit::Normal,
|
||||
QString(),
|
||||
&ok);
|
||||
|
||||
if (ok) {
|
||||
emit registrationWithAuth(mtx::user_interactive::Auth{
|
||||
session,
|
||||
mtx::user_interactive::auth::RegistrationToken{token.toStdString()}});
|
||||
} else {
|
||||
emit errorOccurred();
|
||||
}
|
||||
if (ok) {
|
||||
emit registrationWithAuth(mtx::user_interactive::Auth{
|
||||
session, mtx::user_interactive::auth::RegistrationToken{token.toStdString()}});
|
||||
} else {
|
||||
// use fallback
|
||||
auto dialog = new dialogs::FallbackAuth(
|
||||
QString::fromStdString(current_stage), QString::fromStdString(session), this);
|
||||
|
||||
connect(
|
||||
dialog, &dialogs::FallbackAuth::confirmation, this, [this, session, dialog]() {
|
||||
dialog->close();
|
||||
dialog->deleteLater();
|
||||
emit registrationWithAuth(mtx::user_interactive::Auth{
|
||||
session, mtx::user_interactive::auth::Fallback{}});
|
||||
});
|
||||
|
||||
connect(dialog, &dialogs::FallbackAuth::cancel, this, &RegisterPage::errorOccurred);
|
||||
|
||||
dialog->show();
|
||||
emit errorOccurred();
|
||||
}
|
||||
} else {
|
||||
// use fallback
|
||||
auto dialog = new dialogs::FallbackAuth(
|
||||
QString::fromStdString(current_stage), QString::fromStdString(session), this);
|
||||
|
||||
connect(dialog, &dialogs::FallbackAuth::confirmation, this, [this, session, dialog]() {
|
||||
dialog->close();
|
||||
dialog->deleteLater();
|
||||
emit registrationWithAuth(
|
||||
mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Fallback{}});
|
||||
});
|
||||
|
||||
connect(dialog, &dialogs::FallbackAuth::cancel, this, &RegisterPage::errorOccurred);
|
||||
|
||||
dialog->show();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,76 +21,76 @@ class QHBoxLayout;
|
|||
|
||||
class RegisterPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RegisterPage(QWidget *parent = nullptr);
|
||||
RegisterPage(QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
signals:
|
||||
void backButtonClicked();
|
||||
void errorOccurred();
|
||||
void backButtonClicked();
|
||||
void errorOccurred();
|
||||
|
||||
//! Used to trigger the corresponding slot outside of the main thread.
|
||||
void serverError(const QString &err);
|
||||
//! Used to trigger the corresponding slot outside of the main thread.
|
||||
void serverError(const QString &err);
|
||||
|
||||
void wellKnownLookup();
|
||||
void versionsCheck();
|
||||
void registration();
|
||||
void UIA(const mtx::user_interactive::Unauthorized &unauthorized);
|
||||
void registrationWithAuth(const mtx::user_interactive::Auth &auth);
|
||||
void wellKnownLookup();
|
||||
void versionsCheck();
|
||||
void registration();
|
||||
void UIA(const mtx::user_interactive::Unauthorized &unauthorized);
|
||||
void registrationWithAuth(const mtx::user_interactive::Auth &auth);
|
||||
|
||||
void registering();
|
||||
void registerOk();
|
||||
void registering();
|
||||
void registerOk();
|
||||
|
||||
private slots:
|
||||
void onBackButtonClicked();
|
||||
void onRegisterButtonClicked();
|
||||
void onBackButtonClicked();
|
||||
void onRegisterButtonClicked();
|
||||
|
||||
// function for showing different errors
|
||||
void showError(const QString &msg);
|
||||
void showError(QLabel *label, const QString &msg);
|
||||
// function for showing different errors
|
||||
void showError(const QString &msg);
|
||||
void showError(QLabel *label, const QString &msg);
|
||||
|
||||
bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg);
|
||||
bool checkUsername();
|
||||
bool checkPassword();
|
||||
bool checkPasswordConfirmation();
|
||||
bool checkServer();
|
||||
bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg);
|
||||
bool checkUsername();
|
||||
bool checkPassword();
|
||||
bool checkPasswordConfirmation();
|
||||
bool checkServer();
|
||||
|
||||
void doWellKnownLookup();
|
||||
void doVersionsCheck();
|
||||
void doRegistration();
|
||||
void doUIA(const mtx::user_interactive::Unauthorized &unauthorized);
|
||||
void doRegistrationWithAuth(const mtx::user_interactive::Auth &auth);
|
||||
mtx::http::Callback<mtx::responses::Register> registrationCb();
|
||||
void doWellKnownLookup();
|
||||
void doVersionsCheck();
|
||||
void doRegistration();
|
||||
void doUIA(const mtx::user_interactive::Unauthorized &unauthorized);
|
||||
void doRegistrationWithAuth(const mtx::user_interactive::Auth &auth);
|
||||
mtx::http::Callback<mtx::responses::Register> registrationCb();
|
||||
|
||||
private:
|
||||
QVBoxLayout *top_layout_;
|
||||
QVBoxLayout *top_layout_;
|
||||
|
||||
QHBoxLayout *back_layout_;
|
||||
QHBoxLayout *logo_layout_;
|
||||
QHBoxLayout *button_layout_;
|
||||
QHBoxLayout *back_layout_;
|
||||
QHBoxLayout *logo_layout_;
|
||||
QHBoxLayout *button_layout_;
|
||||
|
||||
QLabel *logo_;
|
||||
QLabel *error_label_;
|
||||
QLabel *error_username_label_;
|
||||
QLabel *error_password_label_;
|
||||
QLabel *error_password_confirmation_label_;
|
||||
QLabel *error_server_label_;
|
||||
QLabel *error_registration_token_label_;
|
||||
QLabel *logo_;
|
||||
QLabel *error_label_;
|
||||
QLabel *error_username_label_;
|
||||
QLabel *error_password_label_;
|
||||
QLabel *error_password_confirmation_label_;
|
||||
QLabel *error_server_label_;
|
||||
QLabel *error_registration_token_label_;
|
||||
|
||||
FlatButton *back_button_;
|
||||
RaisedButton *register_button_;
|
||||
FlatButton *back_button_;
|
||||
RaisedButton *register_button_;
|
||||
|
||||
QWidget *form_widget_;
|
||||
QHBoxLayout *form_wrapper_;
|
||||
QVBoxLayout *form_layout_;
|
||||
QWidget *form_widget_;
|
||||
QHBoxLayout *form_wrapper_;
|
||||
QVBoxLayout *form_layout_;
|
||||
|
||||
TextField *username_input_;
|
||||
TextField *password_input_;
|
||||
TextField *password_confirmation_;
|
||||
TextField *server_input_;
|
||||
TextField *registration_token_input_;
|
||||
TextField *username_input_;
|
||||
TextField *password_input_;
|
||||
TextField *password_confirmation_;
|
||||
TextField *server_input_;
|
||||
TextField *registration_token_input_;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,207 +12,205 @@ RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &serve
|
|||
: QAbstractListModel(parent)
|
||||
, server_(server)
|
||||
{
|
||||
connect(ChatPage::instance(), &ChatPage::newRoom, this, [this](const QString &roomid) {
|
||||
auto roomid_ = roomid.toStdString();
|
||||
connect(ChatPage::instance(), &ChatPage::newRoom, this, [this](const QString &roomid) {
|
||||
auto roomid_ = roomid.toStdString();
|
||||
|
||||
int i = 0;
|
||||
for (const auto &room : publicRoomsData_) {
|
||||
if (room.room_id == roomid_) {
|
||||
emit dataChanged(index(i), index(i), {Roles::CanJoin});
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
});
|
||||
int i = 0;
|
||||
for (const auto &room : publicRoomsData_) {
|
||||
if (room.room_id == roomid_) {
|
||||
emit dataChanged(index(i), index(i), {Roles::CanJoin});
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
});
|
||||
|
||||
connect(this,
|
||||
&RoomDirectoryModel::fetchedRoomsBatch,
|
||||
this,
|
||||
&RoomDirectoryModel::displayRooms,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&RoomDirectoryModel::fetchedRoomsBatch,
|
||||
this,
|
||||
&RoomDirectoryModel::displayRooms,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
RoomDirectoryModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{Roles::Name, "name"},
|
||||
{Roles::Id, "roomid"},
|
||||
{Roles::AvatarUrl, "avatarUrl"},
|
||||
{Roles::Topic, "topic"},
|
||||
{Roles::MemberCount, "numMembers"},
|
||||
{Roles::Previewable, "canPreview"},
|
||||
{Roles::CanJoin, "canJoin"},
|
||||
};
|
||||
return {
|
||||
{Roles::Name, "name"},
|
||||
{Roles::Id, "roomid"},
|
||||
{Roles::AvatarUrl, "avatarUrl"},
|
||||
{Roles::Topic, "topic"},
|
||||
{Roles::MemberCount, "numMembers"},
|
||||
{Roles::Previewable, "canPreview"},
|
||||
{Roles::CanJoin, "canJoin"},
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
RoomDirectoryModel::resetDisplayedData()
|
||||
{
|
||||
beginResetModel();
|
||||
beginResetModel();
|
||||
|
||||
prevBatch_ = "";
|
||||
nextBatch_ = "";
|
||||
canFetchMore_ = true;
|
||||
prevBatch_ = "";
|
||||
nextBatch_ = "";
|
||||
canFetchMore_ = true;
|
||||
|
||||
publicRoomsData_.clear();
|
||||
publicRoomsData_.clear();
|
||||
|
||||
endResetModel();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void
|
||||
RoomDirectoryModel::setMatrixServer(const QString &s)
|
||||
{
|
||||
server_ = s.toStdString();
|
||||
server_ = s.toStdString();
|
||||
|
||||
nhlog::ui()->debug("Received matrix server: {}", server_);
|
||||
nhlog::ui()->debug("Received matrix server: {}", server_);
|
||||
|
||||
resetDisplayedData();
|
||||
resetDisplayedData();
|
||||
}
|
||||
|
||||
void
|
||||
RoomDirectoryModel::setSearchTerm(const QString &f)
|
||||
{
|
||||
userSearchString_ = f.toStdString();
|
||||
userSearchString_ = f.toStdString();
|
||||
|
||||
nhlog::ui()->debug("Received user query: {}", userSearchString_);
|
||||
nhlog::ui()->debug("Received user query: {}", userSearchString_);
|
||||
|
||||
resetDisplayedData();
|
||||
resetDisplayedData();
|
||||
}
|
||||
|
||||
bool
|
||||
RoomDirectoryModel::canJoinRoom(const QString &room) const
|
||||
{
|
||||
return !room.isEmpty() && cache::getRoomInfo({room.toStdString()}).empty();
|
||||
return !room.isEmpty() && cache::getRoomInfo({room.toStdString()}).empty();
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
RoomDirectoryModel::getViasForRoom(const std::vector<std::string> &aliases)
|
||||
{
|
||||
std::vector<std::string> vias;
|
||||
std::vector<std::string> vias;
|
||||
|
||||
vias.reserve(aliases.size());
|
||||
vias.reserve(aliases.size());
|
||||
|
||||
std::transform(aliases.begin(),
|
||||
aliases.end(),
|
||||
std::back_inserter(vias),
|
||||
[](const auto &alias) { return alias.substr(alias.find(":") + 1); });
|
||||
std::transform(aliases.begin(), aliases.end(), std::back_inserter(vias), [](const auto &alias) {
|
||||
return alias.substr(alias.find(":") + 1);
|
||||
});
|
||||
|
||||
// When joining a room hosted on a homeserver other than the one the
|
||||
// account has been registered on, the room's server has to be explicitly
|
||||
// specified in the "server_name=..." URL parameter of the Matrix Join Room
|
||||
// request. For more details consult the specs:
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias
|
||||
if (!server_.empty()) {
|
||||
vias.push_back(server_);
|
||||
}
|
||||
// When joining a room hosted on a homeserver other than the one the
|
||||
// account has been registered on, the room's server has to be explicitly
|
||||
// specified in the "server_name=..." URL parameter of the Matrix Join Room
|
||||
// request. For more details consult the specs:
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias
|
||||
if (!server_.empty()) {
|
||||
vias.push_back(server_);
|
||||
}
|
||||
|
||||
return vias;
|
||||
return vias;
|
||||
}
|
||||
|
||||
void
|
||||
RoomDirectoryModel::joinRoom(const int &index)
|
||||
{
|
||||
if (index >= 0 && static_cast<size_t>(index) < publicRoomsData_.size()) {
|
||||
const auto &chunk = publicRoomsData_[index];
|
||||
nhlog::ui()->debug("'Joining room {}", chunk.room_id);
|
||||
ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases));
|
||||
}
|
||||
if (index >= 0 && static_cast<size_t>(index) < publicRoomsData_.size()) {
|
||||
const auto &chunk = publicRoomsData_[index];
|
||||
nhlog::ui()->debug("'Joining room {}", chunk.room_id);
|
||||
ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases));
|
||||
}
|
||||
}
|
||||
|
||||
QVariant
|
||||
RoomDirectoryModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
const auto &room_chunk = publicRoomsData_[index.row()];
|
||||
switch (role) {
|
||||
case Roles::Name:
|
||||
return QString::fromStdString(room_chunk.name);
|
||||
case Roles::Id:
|
||||
return QString::fromStdString(room_chunk.room_id);
|
||||
case Roles::AvatarUrl:
|
||||
return QString::fromStdString(room_chunk.avatar_url);
|
||||
case Roles::Topic:
|
||||
return QString::fromStdString(room_chunk.topic);
|
||||
case Roles::MemberCount:
|
||||
return QVariant::fromValue(room_chunk.num_joined_members);
|
||||
case Roles::Previewable:
|
||||
return QVariant::fromValue(room_chunk.world_readable);
|
||||
case Roles::CanJoin:
|
||||
return canJoinRoom(QString::fromStdString(room_chunk.room_id));
|
||||
}
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
const auto &room_chunk = publicRoomsData_[index.row()];
|
||||
switch (role) {
|
||||
case Roles::Name:
|
||||
return QString::fromStdString(room_chunk.name);
|
||||
case Roles::Id:
|
||||
return QString::fromStdString(room_chunk.room_id);
|
||||
case Roles::AvatarUrl:
|
||||
return QString::fromStdString(room_chunk.avatar_url);
|
||||
case Roles::Topic:
|
||||
return QString::fromStdString(room_chunk.topic);
|
||||
case Roles::MemberCount:
|
||||
return QVariant::fromValue(room_chunk.num_joined_members);
|
||||
case Roles::Previewable:
|
||||
return QVariant::fromValue(room_chunk.world_readable);
|
||||
case Roles::CanJoin:
|
||||
return canJoinRoom(QString::fromStdString(room_chunk.room_id));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
RoomDirectoryModel::fetchMore(const QModelIndex &)
|
||||
{
|
||||
if (!canFetchMore_)
|
||||
return;
|
||||
if (!canFetchMore_)
|
||||
return;
|
||||
|
||||
nhlog::net()->debug("Fetching more rooms from mtxclient...");
|
||||
nhlog::net()->debug("Fetching more rooms from mtxclient...");
|
||||
|
||||
mtx::requests::PublicRooms req;
|
||||
req.limit = limit_;
|
||||
req.since = prevBatch_;
|
||||
req.filter.generic_search_term = userSearchString_;
|
||||
// req.third_party_instance_id = third_party_instance_id;
|
||||
auto requested_server = server_;
|
||||
mtx::requests::PublicRooms req;
|
||||
req.limit = limit_;
|
||||
req.since = prevBatch_;
|
||||
req.filter.generic_search_term = userSearchString_;
|
||||
// req.third_party_instance_id = third_party_instance_id;
|
||||
auto requested_server = server_;
|
||||
|
||||
reachedEndOfPagination_ = false;
|
||||
emit reachedEndOfPaginationChanged();
|
||||
reachedEndOfPagination_ = false;
|
||||
emit reachedEndOfPaginationChanged();
|
||||
|
||||
loadingMoreRooms_ = true;
|
||||
emit loadingMoreRoomsChanged();
|
||||
loadingMoreRooms_ = true;
|
||||
emit loadingMoreRoomsChanged();
|
||||
|
||||
http::client()->post_public_rooms(
|
||||
req,
|
||||
[requested_server, this, req](const mtx::responses::PublicRooms &res,
|
||||
mtx::http::RequestErr err) {
|
||||
loadingMoreRooms_ = false;
|
||||
emit loadingMoreRoomsChanged();
|
||||
http::client()->post_public_rooms(
|
||||
req,
|
||||
[requested_server, this, req](const mtx::responses::PublicRooms &res,
|
||||
mtx::http::RequestErr err) {
|
||||
loadingMoreRooms_ = false;
|
||||
emit loadingMoreRoomsChanged();
|
||||
|
||||
if (err) {
|
||||
nhlog::net()->error(
|
||||
"Failed to retrieve rooms from mtxclient - {} - {} - {}",
|
||||
mtx::errors::to_string(err->matrix_error.errcode),
|
||||
err->matrix_error.error,
|
||||
err->parse_error);
|
||||
} else if (req.filter.generic_search_term == this->userSearchString_ &&
|
||||
req.since == this->prevBatch_ && requested_server == this->server_) {
|
||||
nhlog::net()->debug("signalling chunk to GUI thread");
|
||||
emit fetchedRoomsBatch(res.chunk, res.next_batch);
|
||||
}
|
||||
},
|
||||
requested_server);
|
||||
if (err) {
|
||||
nhlog::net()->error("Failed to retrieve rooms from mtxclient - {} - {} - {}",
|
||||
mtx::errors::to_string(err->matrix_error.errcode),
|
||||
err->matrix_error.error,
|
||||
err->parse_error);
|
||||
} else if (req.filter.generic_search_term == this->userSearchString_ &&
|
||||
req.since == this->prevBatch_ && requested_server == this->server_) {
|
||||
nhlog::net()->debug("signalling chunk to GUI thread");
|
||||
emit fetchedRoomsBatch(res.chunk, res.next_batch);
|
||||
}
|
||||
},
|
||||
requested_server);
|
||||
}
|
||||
|
||||
void
|
||||
RoomDirectoryModel::displayRooms(std::vector<mtx::responses::PublicRoomsChunk> fetched_rooms,
|
||||
const std::string &next_batch)
|
||||
{
|
||||
nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch);
|
||||
nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch);
|
||||
|
||||
if (fetched_rooms.empty()) {
|
||||
nhlog::net()->error("mtxclient helper thread yielded empty chunk!");
|
||||
return;
|
||||
}
|
||||
if (fetched_rooms.empty()) {
|
||||
nhlog::net()->error("mtxclient helper thread yielded empty chunk!");
|
||||
return;
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(),
|
||||
static_cast<int>(publicRoomsData_.size()),
|
||||
static_cast<int>(publicRoomsData_.size() + fetched_rooms.size()) - 1);
|
||||
this->publicRoomsData_.insert(
|
||||
this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end());
|
||||
endInsertRows();
|
||||
beginInsertRows(QModelIndex(),
|
||||
static_cast<int>(publicRoomsData_.size()),
|
||||
static_cast<int>(publicRoomsData_.size() + fetched_rooms.size()) - 1);
|
||||
this->publicRoomsData_.insert(
|
||||
this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end());
|
||||
endInsertRows();
|
||||
|
||||
if (next_batch.empty()) {
|
||||
canFetchMore_ = false;
|
||||
reachedEndOfPagination_ = true;
|
||||
emit reachedEndOfPaginationChanged();
|
||||
}
|
||||
if (next_batch.empty()) {
|
||||
canFetchMore_ = false;
|
||||
reachedEndOfPagination_ = true;
|
||||
emit reachedEndOfPaginationChanged();
|
||||
}
|
||||
|
||||
prevBatch_ = next_batch;
|
||||
prevBatch_ = next_batch;
|
||||
|
||||
nhlog::ui()->debug("Finished loading rooms");
|
||||
nhlog::ui()->debug("Finished loading rooms");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,74 +25,74 @@ struct PublicRooms;
|
|||
|
||||
class RoomDirectoryModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged)
|
||||
Q_PROPERTY(bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY
|
||||
reachedEndOfPaginationChanged)
|
||||
Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged)
|
||||
Q_PROPERTY(
|
||||
bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY reachedEndOfPaginationChanged)
|
||||
|
||||
public:
|
||||
explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &server = "");
|
||||
explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &server = "");
|
||||
|
||||
enum Roles
|
||||
{
|
||||
Name = Qt::UserRole,
|
||||
Id,
|
||||
AvatarUrl,
|
||||
Topic,
|
||||
MemberCount,
|
||||
Previewable,
|
||||
CanJoin,
|
||||
};
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
enum Roles
|
||||
{
|
||||
Name = Qt::UserRole,
|
||||
Id,
|
||||
AvatarUrl,
|
||||
Topic,
|
||||
MemberCount,
|
||||
Previewable,
|
||||
CanJoin,
|
||||
};
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
inline int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
(void)parent;
|
||||
return static_cast<int>(publicRoomsData_.size());
|
||||
}
|
||||
inline int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
(void)parent;
|
||||
return static_cast<int>(publicRoomsData_.size());
|
||||
}
|
||||
|
||||
bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; }
|
||||
bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; }
|
||||
|
||||
bool loadingMoreRooms() const { return loadingMoreRooms_; }
|
||||
bool loadingMoreRooms() const { return loadingMoreRooms_; }
|
||||
|
||||
bool reachedEndOfPagination() const { return reachedEndOfPagination_; }
|
||||
bool reachedEndOfPagination() const { return reachedEndOfPagination_; }
|
||||
|
||||
void fetchMore(const QModelIndex &) override;
|
||||
void fetchMore(const QModelIndex &) override;
|
||||
|
||||
Q_INVOKABLE void joinRoom(const int &index = -1);
|
||||
Q_INVOKABLE void joinRoom(const int &index = -1);
|
||||
|
||||
signals:
|
||||
void fetchedRoomsBatch(std::vector<mtx::responses::PublicRoomsChunk> rooms,
|
||||
const std::string &next_batch);
|
||||
void loadingMoreRoomsChanged();
|
||||
void reachedEndOfPaginationChanged();
|
||||
void fetchedRoomsBatch(std::vector<mtx::responses::PublicRoomsChunk> rooms,
|
||||
const std::string &next_batch);
|
||||
void loadingMoreRoomsChanged();
|
||||
void reachedEndOfPaginationChanged();
|
||||
|
||||
public slots:
|
||||
void setMatrixServer(const QString &s = "");
|
||||
void setSearchTerm(const QString &f);
|
||||
void setMatrixServer(const QString &s = "");
|
||||
void setSearchTerm(const QString &f);
|
||||
|
||||
private slots:
|
||||
|
||||
void displayRooms(std::vector<mtx::responses::PublicRoomsChunk> rooms,
|
||||
const std::string &next_batch);
|
||||
void displayRooms(std::vector<mtx::responses::PublicRoomsChunk> rooms,
|
||||
const std::string &next_batch);
|
||||
|
||||
private:
|
||||
bool canJoinRoom(const QString &room) const;
|
||||
bool canJoinRoom(const QString &room) const;
|
||||
|
||||
static constexpr size_t limit_ = 50;
|
||||
static constexpr size_t limit_ = 50;
|
||||
|
||||
std::string server_;
|
||||
std::string userSearchString_;
|
||||
std::string prevBatch_;
|
||||
std::string nextBatch_;
|
||||
bool canFetchMore_{true};
|
||||
bool loadingMoreRooms_{false};
|
||||
bool reachedEndOfPagination_{false};
|
||||
std::vector<mtx::responses::PublicRoomsChunk> publicRoomsData_;
|
||||
std::string server_;
|
||||
std::string userSearchString_;
|
||||
std::string prevBatch_;
|
||||
std::string nextBatch_;
|
||||
bool canFetchMore_{true};
|
||||
bool loadingMoreRooms_{false};
|
||||
bool reachedEndOfPagination_{false};
|
||||
std::vector<mtx::responses::PublicRoomsChunk> publicRoomsData_;
|
||||
|
||||
std::vector<std::string> getViasForRoom(const std::vector<std::string> &room);
|
||||
void resetDisplayedData();
|
||||
std::vector<std::string> getViasForRoom(const std::vector<std::string> &room);
|
||||
void resetDisplayedData();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,71 +14,67 @@ RoomsModel::RoomsModel(bool showOnlyRoomWithAliases, QObject *parent)
|
|||
: QAbstractListModel(parent)
|
||||
, showOnlyRoomWithAliases_(showOnlyRoomWithAliases)
|
||||
{
|
||||
std::vector<std::string> rooms_ = cache::joinedRooms();
|
||||
roomInfos = cache::getRoomInfo(rooms_);
|
||||
if (!showOnlyRoomWithAliases_) {
|
||||
roomids.reserve(rooms_.size());
|
||||
roomAliases.reserve(rooms_.size());
|
||||
}
|
||||
std::vector<std::string> rooms_ = cache::joinedRooms();
|
||||
roomInfos = cache::getRoomInfo(rooms_);
|
||||
if (!showOnlyRoomWithAliases_) {
|
||||
roomids.reserve(rooms_.size());
|
||||
roomAliases.reserve(rooms_.size());
|
||||
}
|
||||
|
||||
for (const auto &r : rooms_) {
|
||||
auto roomAliasesList = cache::client()->getRoomAliases(r);
|
||||
for (const auto &r : rooms_) {
|
||||
auto roomAliasesList = cache::client()->getRoomAliases(r);
|
||||
|
||||
if (showOnlyRoomWithAliases_) {
|
||||
if (roomAliasesList && !roomAliasesList->alias.empty()) {
|
||||
roomids.push_back(QString::fromStdString(r));
|
||||
roomAliases.push_back(
|
||||
QString::fromStdString(roomAliasesList->alias));
|
||||
}
|
||||
} else {
|
||||
roomids.push_back(QString::fromStdString(r));
|
||||
roomAliases.push_back(
|
||||
roomAliasesList ? QString::fromStdString(roomAliasesList->alias) : "");
|
||||
}
|
||||
if (showOnlyRoomWithAliases_) {
|
||||
if (roomAliasesList && !roomAliasesList->alias.empty()) {
|
||||
roomids.push_back(QString::fromStdString(r));
|
||||
roomAliases.push_back(QString::fromStdString(roomAliasesList->alias));
|
||||
}
|
||||
} else {
|
||||
roomids.push_back(QString::fromStdString(r));
|
||||
roomAliases.push_back(roomAliasesList ? QString::fromStdString(roomAliasesList->alias)
|
||||
: "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
RoomsModel::roleNames() const
|
||||
{
|
||||
return {{CompletionModel::CompletionRole, "completionRole"},
|
||||
{CompletionModel::SearchRole, "searchRole"},
|
||||
{CompletionModel::SearchRole2, "searchRole2"},
|
||||
{Roles::RoomAlias, "roomAlias"},
|
||||
{Roles::AvatarUrl, "avatarUrl"},
|
||||
{Roles::RoomID, "roomid"},
|
||||
{Roles::RoomName, "roomName"}};
|
||||
return {{CompletionModel::CompletionRole, "completionRole"},
|
||||
{CompletionModel::SearchRole, "searchRole"},
|
||||
{CompletionModel::SearchRole2, "searchRole2"},
|
||||
{Roles::RoomAlias, "roomAlias"},
|
||||
{Roles::AvatarUrl, "avatarUrl"},
|
||||
{Roles::RoomID, "roomid"},
|
||||
{Roles::RoomName, "roomName"}};
|
||||
}
|
||||
|
||||
QVariant
|
||||
RoomsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
switch (role) {
|
||||
case CompletionModel::CompletionRole: {
|
||||
if (UserSettings::instance()->markdown()) {
|
||||
QString percentEncoding =
|
||||
QUrl::toPercentEncoding(roomAliases[index.row()]);
|
||||
return QString("[%1](https://matrix.to/#/%2)")
|
||||
.arg(roomAliases[index.row()], percentEncoding);
|
||||
} else {
|
||||
return roomAliases[index.row()];
|
||||
}
|
||||
}
|
||||
case CompletionModel::SearchRole:
|
||||
case Qt::DisplayRole:
|
||||
case Roles::RoomAlias:
|
||||
return roomAliases[index.row()].toHtmlEscaped();
|
||||
case CompletionModel::SearchRole2:
|
||||
case Roles::RoomName:
|
||||
return QString::fromStdString(roomInfos.at(roomids[index.row()]).name)
|
||||
.toHtmlEscaped();
|
||||
case Roles::AvatarUrl:
|
||||
return QString::fromStdString(
|
||||
roomInfos.at(roomids[index.row()]).avatar_url);
|
||||
case Roles::RoomID:
|
||||
return roomids[index.row()].toHtmlEscaped();
|
||||
}
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
switch (role) {
|
||||
case CompletionModel::CompletionRole: {
|
||||
if (UserSettings::instance()->markdown()) {
|
||||
QString percentEncoding = QUrl::toPercentEncoding(roomAliases[index.row()]);
|
||||
return QString("[%1](https://matrix.to/#/%2)")
|
||||
.arg(roomAliases[index.row()], percentEncoding);
|
||||
} else {
|
||||
return roomAliases[index.row()];
|
||||
}
|
||||
}
|
||||
return {};
|
||||
case CompletionModel::SearchRole:
|
||||
case Qt::DisplayRole:
|
||||
case Roles::RoomAlias:
|
||||
return roomAliases[index.row()].toHtmlEscaped();
|
||||
case CompletionModel::SearchRole2:
|
||||
case Roles::RoomName:
|
||||
return QString::fromStdString(roomInfos.at(roomids[index.row()]).name).toHtmlEscaped();
|
||||
case Roles::AvatarUrl:
|
||||
return QString::fromStdString(roomInfos.at(roomids[index.row()]).avatar_url);
|
||||
case Roles::RoomID:
|
||||
return roomids[index.row()].toHtmlEscaped();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,26 +12,26 @@
|
|||
class RoomsModel : public QAbstractListModel
|
||||
{
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
AvatarUrl = Qt::UserRole,
|
||||
RoomAlias,
|
||||
RoomID,
|
||||
RoomName,
|
||||
};
|
||||
enum Roles
|
||||
{
|
||||
AvatarUrl = Qt::UserRole,
|
||||
RoomAlias,
|
||||
RoomID,
|
||||
RoomName,
|
||||
};
|
||||
|
||||
RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
(void)parent;
|
||||
return (int)roomids.size();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
(void)parent;
|
||||
return (int)roomids.size();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
private:
|
||||
std::vector<QString> roomids;
|
||||
std::vector<QString> roomAliases;
|
||||
std::map<QString, RoomInfo> roomInfos;
|
||||
bool showOnlyRoomWithAliases_;
|
||||
std::vector<QString> roomids;
|
||||
std::vector<QString> roomAliases;
|
||||
std::map<QString, RoomInfo> roomInfos;
|
||||
bool showOnlyRoomWithAliases_;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,46 +12,46 @@
|
|||
|
||||
SSOHandler::SSOHandler(QObject *)
|
||||
{
|
||||
QTimer::singleShot(120000, this, &SSOHandler::ssoFailed);
|
||||
QTimer::singleShot(120000, this, &SSOHandler::ssoFailed);
|
||||
|
||||
using namespace httplib;
|
||||
using namespace httplib;
|
||||
|
||||
svr.set_logger([](const Request &req, const Response &res) {
|
||||
nhlog::net()->info("req: {}, res: {}", req.path, res.status);
|
||||
});
|
||||
svr.set_logger([](const Request &req, const Response &res) {
|
||||
nhlog::net()->info("req: {}, res: {}", req.path, res.status);
|
||||
});
|
||||
|
||||
svr.Get("/sso", [this](const Request &req, Response &res) {
|
||||
if (req.has_param("loginToken")) {
|
||||
auto val = req.get_param_value("loginToken");
|
||||
res.set_content("SSO success", "text/plain");
|
||||
emit ssoSuccess(val);
|
||||
} else {
|
||||
res.set_content("Missing loginToken for SSO login!", "text/plain");
|
||||
emit ssoFailed();
|
||||
}
|
||||
});
|
||||
|
||||
std::thread t([this]() {
|
||||
this->port = svr.bind_to_any_port("localhost");
|
||||
svr.listen_after_bind();
|
||||
});
|
||||
t.detach();
|
||||
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
svr.Get("/sso", [this](const Request &req, Response &res) {
|
||||
if (req.has_param("loginToken")) {
|
||||
auto val = req.get_param_value("loginToken");
|
||||
res.set_content("SSO success", "text/plain");
|
||||
emit ssoSuccess(val);
|
||||
} else {
|
||||
res.set_content("Missing loginToken for SSO login!", "text/plain");
|
||||
emit ssoFailed();
|
||||
}
|
||||
});
|
||||
|
||||
std::thread t([this]() {
|
||||
this->port = svr.bind_to_any_port("localhost");
|
||||
svr.listen_after_bind();
|
||||
});
|
||||
t.detach();
|
||||
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
SSOHandler::~SSOHandler()
|
||||
{
|
||||
svr.stop();
|
||||
while (svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
svr.stop();
|
||||
while (svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
SSOHandler::url() const
|
||||
{
|
||||
return "http://localhost:" + std::to_string(port) + "/sso";
|
||||
return "http://localhost:" + std::to_string(port) + "/sso";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@
|
|||
|
||||
class SSOHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SSOHandler(QObject *parent = nullptr);
|
||||
SSOHandler(QObject *parent = nullptr);
|
||||
|
||||
~SSOHandler();
|
||||
~SSOHandler();
|
||||
|
||||
std::string url() const;
|
||||
std::string url() const;
|
||||
|
||||
signals:
|
||||
void ssoSuccess(std::string token);
|
||||
void ssoFailed();
|
||||
void ssoSuccess(std::string token);
|
||||
void ssoFailed();
|
||||
|
||||
private:
|
||||
httplib::Server svr;
|
||||
int port = 0;
|
||||
httplib::Server svr;
|
||||
int port = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,344 +24,336 @@ SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent)
|
|||
, old_statekey_(statekey_)
|
||||
, pack(std::move(pack_.pack))
|
||||
{
|
||||
[[maybe_unused]] static auto imageInfoType = qRegisterMetaType<mtx::common::ImageInfo>();
|
||||
[[maybe_unused]] static auto imageInfoType = qRegisterMetaType<mtx::common::ImageInfo>();
|
||||
|
||||
if (!pack.pack)
|
||||
pack.pack = mtx::events::msc2545::ImagePack::PackDescription{};
|
||||
if (!pack.pack)
|
||||
pack.pack = mtx::events::msc2545::ImagePack::PackDescription{};
|
||||
|
||||
for (const auto &e : pack.images)
|
||||
shortcodes.push_back(e.first);
|
||||
for (const auto &e : pack.images)
|
||||
shortcodes.push_back(e.first);
|
||||
|
||||
connect(this, &SingleImagePackModel::addImage, this, &SingleImagePackModel::addImageCb);
|
||||
connect(this, &SingleImagePackModel::addImage, this, &SingleImagePackModel::addImageCb);
|
||||
}
|
||||
|
||||
int
|
||||
SingleImagePackModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return (int)shortcodes.size();
|
||||
return (int)shortcodes.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
SingleImagePackModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{Roles::Url, "url"},
|
||||
{Roles::ShortCode, "shortCode"},
|
||||
{Roles::Body, "body"},
|
||||
{Roles::IsEmote, "isEmote"},
|
||||
{Roles::IsSticker, "isSticker"},
|
||||
};
|
||||
return {
|
||||
{Roles::Url, "url"},
|
||||
{Roles::ShortCode, "shortCode"},
|
||||
{Roles::Body, "body"},
|
||||
{Roles::IsEmote, "isEmote"},
|
||||
{Roles::IsSticker, "isSticker"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
SingleImagePackModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
const auto &img = pack.images.at(shortcodes.at(index.row()));
|
||||
switch (role) {
|
||||
case Url:
|
||||
return QString::fromStdString(img.url);
|
||||
case ShortCode:
|
||||
return QString::fromStdString(shortcodes.at(index.row()));
|
||||
case Body:
|
||||
return QString::fromStdString(img.body);
|
||||
case IsEmote:
|
||||
return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji();
|
||||
case IsSticker:
|
||||
return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
const auto &img = pack.images.at(shortcodes.at(index.row()));
|
||||
switch (role) {
|
||||
case Url:
|
||||
return QString::fromStdString(img.url);
|
||||
case ShortCode:
|
||||
return QString::fromStdString(shortcodes.at(index.row()));
|
||||
case Body:
|
||||
return QString::fromStdString(img.body);
|
||||
case IsEmote:
|
||||
return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji();
|
||||
case IsSticker:
|
||||
return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool
|
||||
SingleImagePackModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
using mtx::events::msc2545::PackUsage;
|
||||
using mtx::events::msc2545::PackUsage;
|
||||
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
auto &img = pack.images.at(shortcodes.at(index.row()));
|
||||
switch (role) {
|
||||
case ShortCode: {
|
||||
auto newCode = value.toString().toStdString();
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
auto &img = pack.images.at(shortcodes.at(index.row()));
|
||||
switch (role) {
|
||||
case ShortCode: {
|
||||
auto newCode = value.toString().toStdString();
|
||||
|
||||
// otherwise we delete this by accident
|
||||
if (pack.images.count(newCode))
|
||||
return false;
|
||||
// otherwise we delete this by accident
|
||||
if (pack.images.count(newCode))
|
||||
return false;
|
||||
|
||||
auto tmp = img;
|
||||
auto oldCode = shortcodes.at(index.row());
|
||||
pack.images.erase(oldCode);
|
||||
shortcodes[index.row()] = newCode;
|
||||
pack.images.insert({newCode, tmp});
|
||||
auto tmp = img;
|
||||
auto oldCode = shortcodes.at(index.row());
|
||||
pack.images.erase(oldCode);
|
||||
shortcodes[index.row()] = newCode;
|
||||
pack.images.insert({newCode, tmp});
|
||||
|
||||
emit dataChanged(
|
||||
this->index(index.row()), this->index(index.row()), {Roles::ShortCode});
|
||||
return true;
|
||||
}
|
||||
case Body:
|
||||
img.body = value.toString().toStdString();
|
||||
emit dataChanged(
|
||||
this->index(index.row()), this->index(index.row()), {Roles::Body});
|
||||
return true;
|
||||
case IsEmote: {
|
||||
bool isEmote = value.toBool();
|
||||
bool isSticker =
|
||||
img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker();
|
||||
|
||||
img.usage.set(PackUsage::Emoji, isEmote);
|
||||
img.usage.set(PackUsage::Sticker, isSticker);
|
||||
|
||||
if (img.usage == pack.pack->usage)
|
||||
img.usage.reset();
|
||||
|
||||
emit dataChanged(
|
||||
this->index(index.row()), this->index(index.row()), {Roles::IsEmote});
|
||||
|
||||
return true;
|
||||
}
|
||||
case IsSticker: {
|
||||
bool isEmote =
|
||||
img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji();
|
||||
bool isSticker = value.toBool();
|
||||
|
||||
img.usage.set(PackUsage::Emoji, isEmote);
|
||||
img.usage.set(PackUsage::Sticker, isSticker);
|
||||
|
||||
if (img.usage == pack.pack->usage)
|
||||
img.usage.reset();
|
||||
|
||||
emit dataChanged(
|
||||
this->index(index.row()), this->index(index.row()), {Roles::IsSticker});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
emit dataChanged(
|
||||
this->index(index.row()), this->index(index.row()), {Roles::ShortCode});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case Body:
|
||||
img.body = value.toString().toStdString();
|
||||
emit dataChanged(this->index(index.row()), this->index(index.row()), {Roles::Body});
|
||||
return true;
|
||||
case IsEmote: {
|
||||
bool isEmote = value.toBool();
|
||||
bool isSticker = img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker();
|
||||
|
||||
img.usage.set(PackUsage::Emoji, isEmote);
|
||||
img.usage.set(PackUsage::Sticker, isSticker);
|
||||
|
||||
if (img.usage == pack.pack->usage)
|
||||
img.usage.reset();
|
||||
|
||||
emit dataChanged(this->index(index.row()), this->index(index.row()), {Roles::IsEmote});
|
||||
|
||||
return true;
|
||||
}
|
||||
case IsSticker: {
|
||||
bool isEmote = img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji();
|
||||
bool isSticker = value.toBool();
|
||||
|
||||
img.usage.set(PackUsage::Emoji, isEmote);
|
||||
img.usage.set(PackUsage::Sticker, isSticker);
|
||||
|
||||
if (img.usage == pack.pack->usage)
|
||||
img.usage.reset();
|
||||
|
||||
emit dataChanged(
|
||||
this->index(index.row()), this->index(index.row()), {Roles::IsSticker});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
SingleImagePackModel::isGloballyEnabled() const
|
||||
{
|
||||
if (auto roomPacks =
|
||||
cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) {
|
||||
if (auto tmp = std::get_if<
|
||||
mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
|
||||
&*roomPacks)) {
|
||||
if (tmp->content.rooms.count(roomid_) &&
|
||||
tmp->content.rooms.at(roomid_).count(statekey_))
|
||||
return true;
|
||||
}
|
||||
if (auto roomPacks = cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) {
|
||||
if (auto tmp =
|
||||
std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
|
||||
&*roomPacks)) {
|
||||
if (tmp->content.rooms.count(roomid_) &&
|
||||
tmp->content.rooms.at(roomid_).count(statekey_))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void
|
||||
SingleImagePackModel::setGloballyEnabled(bool enabled)
|
||||
{
|
||||
mtx::events::msc2545::ImagePackRooms content{};
|
||||
if (auto roomPacks =
|
||||
cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) {
|
||||
if (auto tmp = std::get_if<
|
||||
mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
|
||||
&*roomPacks)) {
|
||||
content = tmp->content;
|
||||
}
|
||||
mtx::events::msc2545::ImagePackRooms content{};
|
||||
if (auto roomPacks = cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) {
|
||||
if (auto tmp =
|
||||
std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
|
||||
&*roomPacks)) {
|
||||
content = tmp->content;
|
||||
}
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
content.rooms[roomid_][statekey_] = {};
|
||||
else
|
||||
content.rooms[roomid_].erase(statekey_);
|
||||
if (enabled)
|
||||
content.rooms[roomid_][statekey_] = {};
|
||||
else
|
||||
content.rooms[roomid_].erase(statekey_);
|
||||
|
||||
http::client()->put_account_data(content, [](mtx::http::RequestErr) {
|
||||
// emit this->globallyEnabledChanged();
|
||||
});
|
||||
http::client()->put_account_data(content, [](mtx::http::RequestErr) {
|
||||
// emit this->globallyEnabledChanged();
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
SingleImagePackModel::canEdit() const
|
||||
{
|
||||
if (roomid_.empty())
|
||||
return true;
|
||||
else
|
||||
return Permissions(QString::fromStdString(roomid_))
|
||||
.canChange(qml_mtx_events::ImagePackInRoom);
|
||||
if (roomid_.empty())
|
||||
return true;
|
||||
else
|
||||
return Permissions(QString::fromStdString(roomid_))
|
||||
.canChange(qml_mtx_events::ImagePackInRoom);
|
||||
}
|
||||
|
||||
void
|
||||
SingleImagePackModel::setPackname(QString val)
|
||||
{
|
||||
auto val_ = val.toStdString();
|
||||
if (val_ != this->pack.pack->display_name) {
|
||||
this->pack.pack->display_name = val_;
|
||||
emit packnameChanged();
|
||||
}
|
||||
auto val_ = val.toStdString();
|
||||
if (val_ != this->pack.pack->display_name) {
|
||||
this->pack.pack->display_name = val_;
|
||||
emit packnameChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SingleImagePackModel::setAttribution(QString val)
|
||||
{
|
||||
auto val_ = val.toStdString();
|
||||
if (val_ != this->pack.pack->attribution) {
|
||||
this->pack.pack->attribution = val_;
|
||||
emit attributionChanged();
|
||||
}
|
||||
auto val_ = val.toStdString();
|
||||
if (val_ != this->pack.pack->attribution) {
|
||||
this->pack.pack->attribution = val_;
|
||||
emit attributionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SingleImagePackModel::setAvatarUrl(QString val)
|
||||
{
|
||||
auto val_ = val.toStdString();
|
||||
if (val_ != this->pack.pack->avatar_url) {
|
||||
this->pack.pack->avatar_url = val_;
|
||||
emit avatarUrlChanged();
|
||||
}
|
||||
auto val_ = val.toStdString();
|
||||
if (val_ != this->pack.pack->avatar_url) {
|
||||
this->pack.pack->avatar_url = val_;
|
||||
emit avatarUrlChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SingleImagePackModel::setStatekey(QString val)
|
||||
{
|
||||
auto val_ = val.toStdString();
|
||||
if (val_ != statekey_) {
|
||||
statekey_ = val_;
|
||||
emit statekeyChanged();
|
||||
}
|
||||
auto val_ = val.toStdString();
|
||||
if (val_ != statekey_) {
|
||||
statekey_ = val_;
|
||||
emit statekeyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SingleImagePackModel::setIsStickerPack(bool val)
|
||||
{
|
||||
using mtx::events::msc2545::PackUsage;
|
||||
if (val != pack.pack->is_sticker()) {
|
||||
pack.pack->usage.set(PackUsage::Sticker, val);
|
||||
emit isStickerPackChanged();
|
||||
}
|
||||
using mtx::events::msc2545::PackUsage;
|
||||
if (val != pack.pack->is_sticker()) {
|
||||
pack.pack->usage.set(PackUsage::Sticker, val);
|
||||
emit isStickerPackChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SingleImagePackModel::setIsEmotePack(bool val)
|
||||
{
|
||||
using mtx::events::msc2545::PackUsage;
|
||||
if (val != pack.pack->is_emoji()) {
|
||||
pack.pack->usage.set(PackUsage::Emoji, val);
|
||||
emit isEmotePackChanged();
|
||||
}
|
||||
using mtx::events::msc2545::PackUsage;
|
||||
if (val != pack.pack->is_emoji()) {
|
||||
pack.pack->usage.set(PackUsage::Emoji, val);
|
||||
emit isEmotePackChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SingleImagePackModel::save()
|
||||
{
|
||||
if (roomid_.empty()) {
|
||||
http::client()->put_account_data(pack, [](mtx::http::RequestErr e) {
|
||||
if (e)
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to update image pack: %1")
|
||||
.arg(QString::fromStdString(e->matrix_error.error)));
|
||||
});
|
||||
} else {
|
||||
if (old_statekey_ != statekey_) {
|
||||
http::client()->send_state_event(
|
||||
roomid_,
|
||||
to_string(mtx::events::EventType::ImagePackInRoom),
|
||||
old_statekey_,
|
||||
nlohmann::json::object(),
|
||||
[](const mtx::responses::EventId &, mtx::http::RequestErr e) {
|
||||
if (e)
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to delete old image pack: %1")
|
||||
.arg(QString::fromStdString(e->matrix_error.error)));
|
||||
});
|
||||
}
|
||||
|
||||
http::client()->send_state_event(
|
||||
roomid_,
|
||||
statekey_,
|
||||
pack,
|
||||
[this](const mtx::responses::EventId &, mtx::http::RequestErr e) {
|
||||
if (e)
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to update image pack: %1")
|
||||
.arg(QString::fromStdString(e->matrix_error.error)));
|
||||
|
||||
nhlog::net()->info("Uploaded image pack: %1", statekey_);
|
||||
});
|
||||
if (roomid_.empty()) {
|
||||
http::client()->put_account_data(pack, [](mtx::http::RequestErr e) {
|
||||
if (e)
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to update image pack: %1")
|
||||
.arg(QString::fromStdString(e->matrix_error.error)));
|
||||
});
|
||||
} else {
|
||||
if (old_statekey_ != statekey_) {
|
||||
http::client()->send_state_event(
|
||||
roomid_,
|
||||
to_string(mtx::events::EventType::ImagePackInRoom),
|
||||
old_statekey_,
|
||||
nlohmann::json::object(),
|
||||
[](const mtx::responses::EventId &, mtx::http::RequestErr e) {
|
||||
if (e)
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to delete old image pack: %1")
|
||||
.arg(QString::fromStdString(e->matrix_error.error)));
|
||||
});
|
||||
}
|
||||
|
||||
http::client()->send_state_event(
|
||||
roomid_,
|
||||
statekey_,
|
||||
pack,
|
||||
[this](const mtx::responses::EventId &, mtx::http::RequestErr e) {
|
||||
if (e)
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to update image pack: %1")
|
||||
.arg(QString::fromStdString(e->matrix_error.error)));
|
||||
|
||||
nhlog::net()->info("Uploaded image pack: %1", statekey_);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SingleImagePackModel::addStickers(QList<QUrl> files)
|
||||
{
|
||||
for (const auto &f : files) {
|
||||
auto file = QFile(f.toLocalFile());
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to open image: %1").arg(f.toLocalFile()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto bytes = file.readAll();
|
||||
auto img = utils::readImage(bytes);
|
||||
|
||||
mtx::common::ImageInfo info{};
|
||||
|
||||
auto sz = img.size() / 2;
|
||||
if (sz.width() > 512 || sz.height() > 512) {
|
||||
sz.scale(512, 512, Qt::AspectRatioMode::KeepAspectRatio);
|
||||
} else if (img.height() < 128 && img.width() < 128) {
|
||||
sz = img.size();
|
||||
}
|
||||
|
||||
info.h = sz.height();
|
||||
info.w = sz.width();
|
||||
info.size = bytes.size();
|
||||
info.mimetype =
|
||||
QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString();
|
||||
|
||||
auto filename = f.fileName().toStdString();
|
||||
http::client()->upload(
|
||||
bytes.toStdString(),
|
||||
QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(),
|
||||
filename,
|
||||
[this, filename, info](const mtx::responses::ContentURI &uri,
|
||||
mtx::http::RequestErr e) {
|
||||
if (e) {
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to upload image: %1")
|
||||
.arg(QString::fromStdString(e->matrix_error.error)));
|
||||
return;
|
||||
}
|
||||
|
||||
emit addImage(uri.content_uri, filename, info);
|
||||
});
|
||||
for (const auto &f : files) {
|
||||
auto file = QFile(f.toLocalFile());
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to open image: %1").arg(f.toLocalFile()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto bytes = file.readAll();
|
||||
auto img = utils::readImage(bytes);
|
||||
|
||||
mtx::common::ImageInfo info{};
|
||||
|
||||
auto sz = img.size() / 2;
|
||||
if (sz.width() > 512 || sz.height() > 512) {
|
||||
sz.scale(512, 512, Qt::AspectRatioMode::KeepAspectRatio);
|
||||
} else if (img.height() < 128 && img.width() < 128) {
|
||||
sz = img.size();
|
||||
}
|
||||
|
||||
info.h = sz.height();
|
||||
info.w = sz.width();
|
||||
info.size = bytes.size();
|
||||
info.mimetype = QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString();
|
||||
|
||||
auto filename = f.fileName().toStdString();
|
||||
http::client()->upload(
|
||||
bytes.toStdString(),
|
||||
QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(),
|
||||
filename,
|
||||
[this, filename, info](const mtx::responses::ContentURI &uri, mtx::http::RequestErr e) {
|
||||
if (e) {
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to upload image: %1")
|
||||
.arg(QString::fromStdString(e->matrix_error.error)));
|
||||
return;
|
||||
}
|
||||
|
||||
emit addImage(uri.content_uri, filename, info);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SingleImagePackModel::remove(int idx)
|
||||
{
|
||||
if (idx < (int)shortcodes.size() && idx >= 0) {
|
||||
beginRemoveRows(QModelIndex(), idx, idx);
|
||||
auto s = shortcodes.at(idx);
|
||||
shortcodes.erase(shortcodes.begin() + idx);
|
||||
pack.images.erase(s);
|
||||
endRemoveRows();
|
||||
}
|
||||
if (idx < (int)shortcodes.size() && idx >= 0) {
|
||||
beginRemoveRows(QModelIndex(), idx, idx);
|
||||
auto s = shortcodes.at(idx);
|
||||
shortcodes.erase(shortcodes.begin() + idx);
|
||||
pack.images.erase(s);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SingleImagePackModel::addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info)
|
||||
{
|
||||
mtx::events::msc2545::PackImage img{};
|
||||
img.url = uri;
|
||||
img.info = info;
|
||||
beginInsertRows(
|
||||
QModelIndex(), static_cast<int>(shortcodes.size()), static_cast<int>(shortcodes.size()));
|
||||
mtx::events::msc2545::PackImage img{};
|
||||
img.url = uri;
|
||||
img.info = info;
|
||||
beginInsertRows(
|
||||
QModelIndex(), static_cast<int>(shortcodes.size()), static_cast<int>(shortcodes.size()));
|
||||
|
||||
pack.images[filename] = img;
|
||||
shortcodes.push_back(filename);
|
||||
pack.images[filename] = img;
|
||||
shortcodes.push_back(filename);
|
||||
|
||||
endInsertRows();
|
||||
endInsertRows();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,81 +14,78 @@
|
|||
|
||||
class SingleImagePackModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString roomid READ roomid CONSTANT)
|
||||
Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged)
|
||||
Q_PROPERTY(
|
||||
QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged)
|
||||
Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged)
|
||||
Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged)
|
||||
Q_PROPERTY(
|
||||
bool isStickerPack READ isStickerPack WRITE setIsStickerPack NOTIFY isStickerPackChanged)
|
||||
Q_PROPERTY(bool isEmotePack READ isEmotePack WRITE setIsEmotePack NOTIFY isEmotePackChanged)
|
||||
Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY
|
||||
globallyEnabledChanged)
|
||||
Q_PROPERTY(bool canEdit READ canEdit CONSTANT)
|
||||
Q_PROPERTY(QString roomid READ roomid CONSTANT)
|
||||
Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged)
|
||||
Q_PROPERTY(QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged)
|
||||
Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged)
|
||||
Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged)
|
||||
Q_PROPERTY(
|
||||
bool isStickerPack READ isStickerPack WRITE setIsStickerPack NOTIFY isStickerPackChanged)
|
||||
Q_PROPERTY(bool isEmotePack READ isEmotePack WRITE setIsEmotePack NOTIFY isEmotePackChanged)
|
||||
Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY
|
||||
globallyEnabledChanged)
|
||||
Q_PROPERTY(bool canEdit READ canEdit CONSTANT)
|
||||
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
Url = Qt::UserRole,
|
||||
ShortCode,
|
||||
Body,
|
||||
IsEmote,
|
||||
IsSticker,
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
enum Roles
|
||||
{
|
||||
Url = Qt::UserRole,
|
||||
ShortCode,
|
||||
Body,
|
||||
IsEmote,
|
||||
IsSticker,
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
bool setData(const QModelIndex &index,
|
||||
const QVariant &value,
|
||||
int role = Qt::EditRole) override;
|
||||
SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
||||
|
||||
QString roomid() const { return QString::fromStdString(roomid_); }
|
||||
QString statekey() const { return QString::fromStdString(statekey_); }
|
||||
QString packname() const { return QString::fromStdString(pack.pack->display_name); }
|
||||
QString attribution() const { return QString::fromStdString(pack.pack->attribution); }
|
||||
QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); }
|
||||
bool isStickerPack() const { return pack.pack->is_sticker(); }
|
||||
bool isEmotePack() const { return pack.pack->is_emoji(); }
|
||||
QString roomid() const { return QString::fromStdString(roomid_); }
|
||||
QString statekey() const { return QString::fromStdString(statekey_); }
|
||||
QString packname() const { return QString::fromStdString(pack.pack->display_name); }
|
||||
QString attribution() const { return QString::fromStdString(pack.pack->attribution); }
|
||||
QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); }
|
||||
bool isStickerPack() const { return pack.pack->is_sticker(); }
|
||||
bool isEmotePack() const { return pack.pack->is_emoji(); }
|
||||
|
||||
bool isGloballyEnabled() const;
|
||||
bool canEdit() const;
|
||||
void setGloballyEnabled(bool enabled);
|
||||
bool isGloballyEnabled() const;
|
||||
bool canEdit() const;
|
||||
void setGloballyEnabled(bool enabled);
|
||||
|
||||
void setPackname(QString val);
|
||||
void setAttribution(QString val);
|
||||
void setAvatarUrl(QString val);
|
||||
void setStatekey(QString val);
|
||||
void setIsStickerPack(bool val);
|
||||
void setIsEmotePack(bool val);
|
||||
void setPackname(QString val);
|
||||
void setAttribution(QString val);
|
||||
void setAvatarUrl(QString val);
|
||||
void setStatekey(QString val);
|
||||
void setIsStickerPack(bool val);
|
||||
void setIsEmotePack(bool val);
|
||||
|
||||
Q_INVOKABLE void save();
|
||||
Q_INVOKABLE void addStickers(QList<QUrl> files);
|
||||
Q_INVOKABLE void remove(int index);
|
||||
Q_INVOKABLE void save();
|
||||
Q_INVOKABLE void addStickers(QList<QUrl> files);
|
||||
Q_INVOKABLE void remove(int index);
|
||||
|
||||
signals:
|
||||
void globallyEnabledChanged();
|
||||
void statekeyChanged();
|
||||
void attributionChanged();
|
||||
void packnameChanged();
|
||||
void avatarUrlChanged();
|
||||
void isEmotePackChanged();
|
||||
void isStickerPackChanged();
|
||||
void globallyEnabledChanged();
|
||||
void statekeyChanged();
|
||||
void attributionChanged();
|
||||
void packnameChanged();
|
||||
void avatarUrlChanged();
|
||||
void isEmotePackChanged();
|
||||
void isStickerPackChanged();
|
||||
|
||||
void addImage(std::string uri, std::string filename, mtx::common::ImageInfo info);
|
||||
void addImage(std::string uri, std::string filename, mtx::common::ImageInfo info);
|
||||
|
||||
private slots:
|
||||
void addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info);
|
||||
void addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info);
|
||||
|
||||
private:
|
||||
std::string roomid_;
|
||||
std::string statekey_, old_statekey_;
|
||||
std::string roomid_;
|
||||
std::string statekey_, old_statekey_;
|
||||
|
||||
mtx::events::msc2545::ImagePack pack;
|
||||
std::vector<std::string> shortcodes;
|
||||
mtx::events::msc2545::ImagePack pack;
|
||||
std::vector<std::string> shortcodes;
|
||||
};
|
||||
|
|
|
|||
134
src/TrayIcon.cpp
134
src/TrayIcon.cpp
|
|
@ -19,7 +19,7 @@
|
|||
MsgCountComposedIcon::MsgCountComposedIcon(const QString &filename)
|
||||
: QIconEngine()
|
||||
{
|
||||
icon_ = QIcon(filename);
|
||||
icon_ = QIcon(filename);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -28,95 +28,95 @@ MsgCountComposedIcon::paint(QPainter *painter,
|
|||
QIcon::Mode mode,
|
||||
QIcon::State state)
|
||||
{
|
||||
painter->setRenderHint(QPainter::TextAntialiasing);
|
||||
painter->setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
painter->setRenderHint(QPainter::TextAntialiasing);
|
||||
painter->setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
icon_.paint(painter, rect, Qt::AlignCenter, mode, state);
|
||||
icon_.paint(painter, rect, Qt::AlignCenter, mode, state);
|
||||
|
||||
if (msgCount <= 0)
|
||||
return;
|
||||
if (msgCount <= 0)
|
||||
return;
|
||||
|
||||
QColor backgroundColor("red");
|
||||
QColor textColor("white");
|
||||
QColor backgroundColor("red");
|
||||
QColor textColor("white");
|
||||
|
||||
QBrush brush;
|
||||
brush.setStyle(Qt::SolidPattern);
|
||||
brush.setColor(backgroundColor);
|
||||
QBrush brush;
|
||||
brush.setStyle(Qt::SolidPattern);
|
||||
brush.setColor(backgroundColor);
|
||||
|
||||
QFont f;
|
||||
f.setPointSizeF(8);
|
||||
f.setWeight(QFont::Thin);
|
||||
QFont f;
|
||||
f.setPointSizeF(8);
|
||||
f.setWeight(QFont::Thin);
|
||||
|
||||
painter->setBrush(brush);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setFont(f);
|
||||
painter->setBrush(brush);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setFont(f);
|
||||
|
||||
QRectF bubble(rect.width() - BubbleDiameter,
|
||||
rect.height() - BubbleDiameter,
|
||||
BubbleDiameter,
|
||||
BubbleDiameter);
|
||||
painter->drawEllipse(bubble);
|
||||
painter->setPen(QPen(textColor));
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount));
|
||||
QRectF bubble(rect.width() - BubbleDiameter,
|
||||
rect.height() - BubbleDiameter,
|
||||
BubbleDiameter,
|
||||
BubbleDiameter);
|
||||
painter->drawEllipse(bubble);
|
||||
painter->setPen(QPen(textColor));
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount));
|
||||
}
|
||||
|
||||
QIconEngine *
|
||||
MsgCountComposedIcon::clone() const
|
||||
{
|
||||
return new MsgCountComposedIcon(*this);
|
||||
return new MsgCountComposedIcon(*this);
|
||||
}
|
||||
|
||||
QList<QSize>
|
||||
MsgCountComposedIcon::availableSizes(QIcon::Mode mode, QIcon::State state) const
|
||||
{
|
||||
Q_UNUSED(mode);
|
||||
Q_UNUSED(state);
|
||||
QList<QSize> sizes;
|
||||
sizes.append(QSize(24, 24));
|
||||
sizes.append(QSize(32, 32));
|
||||
sizes.append(QSize(48, 48));
|
||||
sizes.append(QSize(64, 64));
|
||||
sizes.append(QSize(128, 128));
|
||||
sizes.append(QSize(256, 256));
|
||||
return sizes;
|
||||
Q_UNUSED(mode);
|
||||
Q_UNUSED(state);
|
||||
QList<QSize> sizes;
|
||||
sizes.append(QSize(24, 24));
|
||||
sizes.append(QSize(32, 32));
|
||||
sizes.append(QSize(48, 48));
|
||||
sizes.append(QSize(64, 64));
|
||||
sizes.append(QSize(128, 128));
|
||||
sizes.append(QSize(256, 256));
|
||||
return sizes;
|
||||
}
|
||||
|
||||
QPixmap
|
||||
MsgCountComposedIcon::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
QImage img(size, QImage::Format_ARGB32);
|
||||
img.fill(qRgba(0, 0, 0, 0));
|
||||
QPixmap result = QPixmap::fromImage(img, Qt::NoFormatConversion);
|
||||
{
|
||||
QPainter painter(&result);
|
||||
paint(&painter, QRect(QPoint(0, 0), size), mode, state);
|
||||
}
|
||||
return result;
|
||||
QImage img(size, QImage::Format_ARGB32);
|
||||
img.fill(qRgba(0, 0, 0, 0));
|
||||
QPixmap result = QPixmap::fromImage(img, Qt::NoFormatConversion);
|
||||
{
|
||||
QPainter painter(&result);
|
||||
paint(&painter, QRect(QPoint(0, 0), size), mode, state);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
|
||||
: QSystemTrayIcon(parent)
|
||||
{
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
setIcon(QIcon(filename));
|
||||
setIcon(QIcon(filename));
|
||||
#else
|
||||
icon_ = new MsgCountComposedIcon(filename);
|
||||
setIcon(QIcon(icon_));
|
||||
icon_ = new MsgCountComposedIcon(filename);
|
||||
setIcon(QIcon(icon_));
|
||||
#endif
|
||||
|
||||
QMenu *menu = new QMenu(parent);
|
||||
setContextMenu(menu);
|
||||
QMenu *menu = new QMenu(parent);
|
||||
setContextMenu(menu);
|
||||
|
||||
viewAction_ = new QAction(tr("Show"), this);
|
||||
quitAction_ = new QAction(tr("Quit"), this);
|
||||
viewAction_ = new QAction(tr("Show"), this);
|
||||
quitAction_ = new QAction(tr("Quit"), this);
|
||||
|
||||
connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show()));
|
||||
connect(quitAction_, &QAction::triggered, this, QApplication::quit);
|
||||
connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show()));
|
||||
connect(quitAction_, &QAction::triggered, this, QApplication::quit);
|
||||
|
||||
menu->addAction(viewAction_);
|
||||
menu->addAction(quitAction_);
|
||||
menu->addAction(viewAction_);
|
||||
menu->addAction(quitAction_);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -127,25 +127,25 @@ TrayIcon::setUnreadCount(int count)
|
|||
// currently, to avoid writing obj-c code, ignore deprecated warnings on the badge functions
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
auto labelText = count == 0 ? "" : QString::number(count);
|
||||
auto labelText = count == 0 ? "" : QString::number(count);
|
||||
|
||||
if (labelText == QtMac::badgeLabelText())
|
||||
return;
|
||||
if (labelText == QtMac::badgeLabelText())
|
||||
return;
|
||||
|
||||
QtMac::setBadgeLabelText(labelText);
|
||||
QtMac::setBadgeLabelText(labelText);
|
||||
#pragma clang diagnostic pop
|
||||
#elif defined(Q_OS_WIN)
|
||||
// FIXME: Find a way to use Windows apis for the badge counter (if any).
|
||||
#else
|
||||
if (count == icon_->msgCount)
|
||||
return;
|
||||
if (count == icon_->msgCount)
|
||||
return;
|
||||
|
||||
// Custom drawing on Linux.
|
||||
MsgCountComposedIcon *tmp = static_cast<MsgCountComposedIcon *>(icon_->clone());
|
||||
tmp->msgCount = count;
|
||||
// Custom drawing on Linux.
|
||||
MsgCountComposedIcon *tmp = static_cast<MsgCountComposedIcon *>(icon_->clone());
|
||||
tmp->msgCount = count;
|
||||
|
||||
setIcon(QIcon(tmp));
|
||||
setIcon(QIcon(tmp));
|
||||
|
||||
icon_ = tmp;
|
||||
icon_ = tmp;
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,33 +16,33 @@ class QPainter;
|
|||
class MsgCountComposedIcon : public QIconEngine
|
||||
{
|
||||
public:
|
||||
MsgCountComposedIcon(const QString &filename);
|
||||
MsgCountComposedIcon(const QString &filename);
|
||||
|
||||
void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
|
||||
QIconEngine *clone() const override;
|
||||
QList<QSize> availableSizes(QIcon::Mode mode, QIcon::State state) const override;
|
||||
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
|
||||
void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
|
||||
QIconEngine *clone() const override;
|
||||
QList<QSize> availableSizes(QIcon::Mode mode, QIcon::State state) const override;
|
||||
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
|
||||
|
||||
int msgCount = 0;
|
||||
int msgCount = 0;
|
||||
|
||||
private:
|
||||
const int BubbleDiameter = 17;
|
||||
const int BubbleDiameter = 17;
|
||||
|
||||
QIcon icon_;
|
||||
QIcon icon_;
|
||||
};
|
||||
|
||||
class TrayIcon : public QSystemTrayIcon
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
TrayIcon(const QString &filename, QWidget *parent);
|
||||
TrayIcon(const QString &filename, QWidget *parent);
|
||||
|
||||
public slots:
|
||||
void setUnreadCount(int count);
|
||||
void setUnreadCount(int count);
|
||||
|
||||
private:
|
||||
QAction *viewAction_;
|
||||
QAction *quitAction_;
|
||||
QAction *viewAction_;
|
||||
QAction *quitAction_;
|
||||
|
||||
MsgCountComposedIcon *icon_;
|
||||
MsgCountComposedIcon *icon_;
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -30,402 +30,395 @@ constexpr int LayoutBottomMargin = LayoutTopMargin;
|
|||
|
||||
class UserSettings : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged)
|
||||
Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE
|
||||
setMessageHoverHighlight NOTIFY messageHoverHighlightChanged)
|
||||
Q_PROPERTY(bool enlargeEmojiOnlyMessages READ enlargeEmojiOnlyMessages WRITE
|
||||
setEnlargeEmojiOnlyMessages NOTIFY enlargeEmojiOnlyMessagesChanged)
|
||||
Q_PROPERTY(bool tray READ tray WRITE setTray NOTIFY trayChanged)
|
||||
Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged)
|
||||
Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged)
|
||||
Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged)
|
||||
Q_PROPERTY(bool animateImagesOnHover READ animateImagesOnHover WRITE setAnimateImagesOnHover
|
||||
NOTIFY animateImagesOnHoverChanged)
|
||||
Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications
|
||||
NOTIFY typingNotificationsChanged)
|
||||
Q_PROPERTY(bool sortByImportance READ sortByImportance WRITE setSortByImportance NOTIFY
|
||||
roomSortingChanged)
|
||||
Q_PROPERTY(bool buttonsInTimeline READ buttonsInTimeline WRITE setButtonsInTimeline NOTIFY
|
||||
buttonInTimelineChanged)
|
||||
Q_PROPERTY(
|
||||
bool readReceipts READ readReceipts WRITE setReadReceipts NOTIFY readReceiptsChanged)
|
||||
Q_PROPERTY(bool desktopNotifications READ hasDesktopNotifications WRITE
|
||||
setDesktopNotifications NOTIFY desktopNotificationsChanged)
|
||||
Q_PROPERTY(bool alertOnNotification READ hasAlertOnNotification WRITE setAlertOnNotification
|
||||
NOTIFY alertOnNotificationChanged)
|
||||
Q_PROPERTY(
|
||||
bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged)
|
||||
Q_PROPERTY(bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY
|
||||
decryptSidebarChanged)
|
||||
Q_PROPERTY(
|
||||
bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged)
|
||||
Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout
|
||||
NOTIFY privacyScreenTimeoutChanged)
|
||||
Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY
|
||||
timelineMaxWidthChanged)
|
||||
Q_PROPERTY(
|
||||
int roomListWidth READ roomListWidth WRITE setRoomListWidth NOTIFY roomListWidthChanged)
|
||||
Q_PROPERTY(int communityListWidth READ communityListWidth WRITE setCommunityListWidth NOTIFY
|
||||
communityListWidthChanged)
|
||||
Q_PROPERTY(bool mobileMode READ mobileMode WRITE setMobileMode NOTIFY mobileModeChanged)
|
||||
Q_PROPERTY(double fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged)
|
||||
Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged)
|
||||
Q_PROPERTY(
|
||||
QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged)
|
||||
Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged)
|
||||
Q_PROPERTY(QString ringtone READ ringtone WRITE setRingtone NOTIFY ringtoneChanged)
|
||||
Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged)
|
||||
Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged)
|
||||
Q_PROPERTY(QString cameraResolution READ cameraResolution WRITE setCameraResolution NOTIFY
|
||||
cameraResolutionChanged)
|
||||
Q_PROPERTY(QString cameraFrameRate READ cameraFrameRate WRITE setCameraFrameRate NOTIFY
|
||||
cameraFrameRateChanged)
|
||||
Q_PROPERTY(int screenShareFrameRate READ screenShareFrameRate WRITE setScreenShareFrameRate
|
||||
NOTIFY screenShareFrameRateChanged)
|
||||
Q_PROPERTY(bool screenSharePiP READ screenSharePiP WRITE setScreenSharePiP NOTIFY
|
||||
screenSharePiPChanged)
|
||||
Q_PROPERTY(bool screenShareRemoteVideo READ screenShareRemoteVideo WRITE
|
||||
setScreenShareRemoteVideo NOTIFY screenShareRemoteVideoChanged)
|
||||
Q_PROPERTY(bool screenShareHideCursor READ screenShareHideCursor WRITE
|
||||
setScreenShareHideCursor NOTIFY screenShareHideCursorChanged)
|
||||
Q_PROPERTY(
|
||||
bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged)
|
||||
Q_PROPERTY(bool onlyShareKeysWithVerifiedUsers READ onlyShareKeysWithVerifiedUsers WRITE
|
||||
setOnlyShareKeysWithVerifiedUsers NOTIFY onlyShareKeysWithVerifiedUsersChanged)
|
||||
Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE
|
||||
setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged)
|
||||
Q_PROPERTY(bool useOnlineKeyBackup READ useOnlineKeyBackup WRITE setUseOnlineKeyBackup
|
||||
NOTIFY useOnlineKeyBackupChanged)
|
||||
Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged)
|
||||
Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged)
|
||||
Q_PROPERTY(
|
||||
QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged)
|
||||
Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged)
|
||||
Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
|
||||
Q_PROPERTY(bool disableCertificateValidation READ disableCertificateValidation WRITE
|
||||
setDisableCertificateValidation NOTIFY disableCertificateValidationChanged)
|
||||
Q_PROPERTY(
|
||||
bool useIdenticon READ useIdenticon WRITE setUseIdenticon NOTIFY useIdenticonChanged)
|
||||
Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged)
|
||||
Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE setMessageHoverHighlight
|
||||
NOTIFY messageHoverHighlightChanged)
|
||||
Q_PROPERTY(bool enlargeEmojiOnlyMessages READ enlargeEmojiOnlyMessages WRITE
|
||||
setEnlargeEmojiOnlyMessages NOTIFY enlargeEmojiOnlyMessagesChanged)
|
||||
Q_PROPERTY(bool tray READ tray WRITE setTray NOTIFY trayChanged)
|
||||
Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged)
|
||||
Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged)
|
||||
Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged)
|
||||
Q_PROPERTY(bool animateImagesOnHover READ animateImagesOnHover WRITE setAnimateImagesOnHover
|
||||
NOTIFY animateImagesOnHoverChanged)
|
||||
Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications NOTIFY
|
||||
typingNotificationsChanged)
|
||||
Q_PROPERTY(bool sortByImportance READ sortByImportance WRITE setSortByImportance NOTIFY
|
||||
roomSortingChanged)
|
||||
Q_PROPERTY(bool buttonsInTimeline READ buttonsInTimeline WRITE setButtonsInTimeline NOTIFY
|
||||
buttonInTimelineChanged)
|
||||
Q_PROPERTY(bool readReceipts READ readReceipts WRITE setReadReceipts NOTIFY readReceiptsChanged)
|
||||
Q_PROPERTY(bool desktopNotifications READ hasDesktopNotifications WRITE setDesktopNotifications
|
||||
NOTIFY desktopNotificationsChanged)
|
||||
Q_PROPERTY(bool alertOnNotification READ hasAlertOnNotification WRITE setAlertOnNotification
|
||||
NOTIFY alertOnNotificationChanged)
|
||||
Q_PROPERTY(
|
||||
bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged)
|
||||
Q_PROPERTY(
|
||||
bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY decryptSidebarChanged)
|
||||
Q_PROPERTY(
|
||||
bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged)
|
||||
Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout
|
||||
NOTIFY privacyScreenTimeoutChanged)
|
||||
Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY
|
||||
timelineMaxWidthChanged)
|
||||
Q_PROPERTY(
|
||||
int roomListWidth READ roomListWidth WRITE setRoomListWidth NOTIFY roomListWidthChanged)
|
||||
Q_PROPERTY(int communityListWidth READ communityListWidth WRITE setCommunityListWidth NOTIFY
|
||||
communityListWidthChanged)
|
||||
Q_PROPERTY(bool mobileMode READ mobileMode WRITE setMobileMode NOTIFY mobileModeChanged)
|
||||
Q_PROPERTY(double fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged)
|
||||
Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged)
|
||||
Q_PROPERTY(QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged)
|
||||
Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged)
|
||||
Q_PROPERTY(QString ringtone READ ringtone WRITE setRingtone NOTIFY ringtoneChanged)
|
||||
Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged)
|
||||
Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged)
|
||||
Q_PROPERTY(QString cameraResolution READ cameraResolution WRITE setCameraResolution NOTIFY
|
||||
cameraResolutionChanged)
|
||||
Q_PROPERTY(QString cameraFrameRate READ cameraFrameRate WRITE setCameraFrameRate NOTIFY
|
||||
cameraFrameRateChanged)
|
||||
Q_PROPERTY(int screenShareFrameRate READ screenShareFrameRate WRITE setScreenShareFrameRate
|
||||
NOTIFY screenShareFrameRateChanged)
|
||||
Q_PROPERTY(
|
||||
bool screenSharePiP READ screenSharePiP WRITE setScreenSharePiP NOTIFY screenSharePiPChanged)
|
||||
Q_PROPERTY(bool screenShareRemoteVideo READ screenShareRemoteVideo WRITE
|
||||
setScreenShareRemoteVideo NOTIFY screenShareRemoteVideoChanged)
|
||||
Q_PROPERTY(bool screenShareHideCursor READ screenShareHideCursor WRITE setScreenShareHideCursor
|
||||
NOTIFY screenShareHideCursorChanged)
|
||||
Q_PROPERTY(
|
||||
bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged)
|
||||
Q_PROPERTY(bool onlyShareKeysWithVerifiedUsers READ onlyShareKeysWithVerifiedUsers WRITE
|
||||
setOnlyShareKeysWithVerifiedUsers NOTIFY onlyShareKeysWithVerifiedUsersChanged)
|
||||
Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE
|
||||
setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged)
|
||||
Q_PROPERTY(bool useOnlineKeyBackup READ useOnlineKeyBackup WRITE setUseOnlineKeyBackup NOTIFY
|
||||
useOnlineKeyBackupChanged)
|
||||
Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged)
|
||||
Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged)
|
||||
Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged)
|
||||
Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged)
|
||||
Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
|
||||
Q_PROPERTY(bool disableCertificateValidation READ disableCertificateValidation WRITE
|
||||
setDisableCertificateValidation NOTIFY disableCertificateValidationChanged)
|
||||
Q_PROPERTY(bool useIdenticon READ useIdenticon WRITE setUseIdenticon NOTIFY useIdenticonChanged)
|
||||
|
||||
UserSettings();
|
||||
UserSettings();
|
||||
|
||||
public:
|
||||
static QSharedPointer<UserSettings> instance();
|
||||
static void initialize(std::optional<QString> profile);
|
||||
static QSharedPointer<UserSettings> instance();
|
||||
static void initialize(std::optional<QString> profile);
|
||||
|
||||
QSettings *qsettings() { return &settings; }
|
||||
QSettings *qsettings() { return &settings; }
|
||||
|
||||
enum class Presence
|
||||
{
|
||||
AutomaticPresence,
|
||||
Online,
|
||||
Unavailable,
|
||||
Offline,
|
||||
};
|
||||
Q_ENUM(Presence)
|
||||
enum class Presence
|
||||
{
|
||||
AutomaticPresence,
|
||||
Online,
|
||||
Unavailable,
|
||||
Offline,
|
||||
};
|
||||
Q_ENUM(Presence)
|
||||
|
||||
void save();
|
||||
void load(std::optional<QString> profile);
|
||||
void applyTheme();
|
||||
void setTheme(QString theme);
|
||||
void setMessageHoverHighlight(bool state);
|
||||
void setEnlargeEmojiOnlyMessages(bool state);
|
||||
void setTray(bool state);
|
||||
void setStartInTray(bool state);
|
||||
void setMobileMode(bool mode);
|
||||
void setFontSize(double size);
|
||||
void setFontFamily(QString family);
|
||||
void setEmojiFontFamily(QString family);
|
||||
void setGroupView(bool state);
|
||||
void setMarkdown(bool state);
|
||||
void setAnimateImagesOnHover(bool state);
|
||||
void setReadReceipts(bool state);
|
||||
void setTypingNotifications(bool state);
|
||||
void setSortByImportance(bool state);
|
||||
void setButtonsInTimeline(bool state);
|
||||
void setTimelineMaxWidth(int state);
|
||||
void setCommunityListWidth(int state);
|
||||
void setRoomListWidth(int state);
|
||||
void setDesktopNotifications(bool state);
|
||||
void setAlertOnNotification(bool state);
|
||||
void setAvatarCircles(bool state);
|
||||
void setDecryptSidebar(bool state);
|
||||
void setPrivacyScreen(bool state);
|
||||
void setPrivacyScreenTimeout(int state);
|
||||
void setPresence(Presence state);
|
||||
void setRingtone(QString ringtone);
|
||||
void setMicrophone(QString microphone);
|
||||
void setCamera(QString camera);
|
||||
void setCameraResolution(QString resolution);
|
||||
void setCameraFrameRate(QString frameRate);
|
||||
void setScreenShareFrameRate(int frameRate);
|
||||
void setScreenSharePiP(bool state);
|
||||
void setScreenShareRemoteVideo(bool state);
|
||||
void setScreenShareHideCursor(bool state);
|
||||
void setUseStunServer(bool state);
|
||||
void setOnlyShareKeysWithVerifiedUsers(bool state);
|
||||
void setShareKeysWithTrustedUsers(bool state);
|
||||
void setUseOnlineKeyBackup(bool state);
|
||||
void setProfile(QString profile);
|
||||
void setUserId(QString userId);
|
||||
void setAccessToken(QString accessToken);
|
||||
void setDeviceId(QString deviceId);
|
||||
void setHomeserver(QString homeserver);
|
||||
void setDisableCertificateValidation(bool disabled);
|
||||
void setHiddenTags(QStringList hiddenTags);
|
||||
void setUseIdenticon(bool state);
|
||||
void save();
|
||||
void load(std::optional<QString> profile);
|
||||
void applyTheme();
|
||||
void setTheme(QString theme);
|
||||
void setMessageHoverHighlight(bool state);
|
||||
void setEnlargeEmojiOnlyMessages(bool state);
|
||||
void setTray(bool state);
|
||||
void setStartInTray(bool state);
|
||||
void setMobileMode(bool mode);
|
||||
void setFontSize(double size);
|
||||
void setFontFamily(QString family);
|
||||
void setEmojiFontFamily(QString family);
|
||||
void setGroupView(bool state);
|
||||
void setMarkdown(bool state);
|
||||
void setAnimateImagesOnHover(bool state);
|
||||
void setReadReceipts(bool state);
|
||||
void setTypingNotifications(bool state);
|
||||
void setSortByImportance(bool state);
|
||||
void setButtonsInTimeline(bool state);
|
||||
void setTimelineMaxWidth(int state);
|
||||
void setCommunityListWidth(int state);
|
||||
void setRoomListWidth(int state);
|
||||
void setDesktopNotifications(bool state);
|
||||
void setAlertOnNotification(bool state);
|
||||
void setAvatarCircles(bool state);
|
||||
void setDecryptSidebar(bool state);
|
||||
void setPrivacyScreen(bool state);
|
||||
void setPrivacyScreenTimeout(int state);
|
||||
void setPresence(Presence state);
|
||||
void setRingtone(QString ringtone);
|
||||
void setMicrophone(QString microphone);
|
||||
void setCamera(QString camera);
|
||||
void setCameraResolution(QString resolution);
|
||||
void setCameraFrameRate(QString frameRate);
|
||||
void setScreenShareFrameRate(int frameRate);
|
||||
void setScreenSharePiP(bool state);
|
||||
void setScreenShareRemoteVideo(bool state);
|
||||
void setScreenShareHideCursor(bool state);
|
||||
void setUseStunServer(bool state);
|
||||
void setOnlyShareKeysWithVerifiedUsers(bool state);
|
||||
void setShareKeysWithTrustedUsers(bool state);
|
||||
void setUseOnlineKeyBackup(bool state);
|
||||
void setProfile(QString profile);
|
||||
void setUserId(QString userId);
|
||||
void setAccessToken(QString accessToken);
|
||||
void setDeviceId(QString deviceId);
|
||||
void setHomeserver(QString homeserver);
|
||||
void setDisableCertificateValidation(bool disabled);
|
||||
void setHiddenTags(QStringList hiddenTags);
|
||||
void setUseIdenticon(bool state);
|
||||
|
||||
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
|
||||
bool messageHoverHighlight() const { return messageHoverHighlight_; }
|
||||
bool enlargeEmojiOnlyMessages() const { return enlargeEmojiOnlyMessages_; }
|
||||
bool tray() const { return tray_; }
|
||||
bool startInTray() const { return startInTray_; }
|
||||
bool groupView() const { return groupView_; }
|
||||
bool avatarCircles() const { return avatarCircles_; }
|
||||
bool decryptSidebar() const { return decryptSidebar_; }
|
||||
bool privacyScreen() const { return privacyScreen_; }
|
||||
int privacyScreenTimeout() const { return privacyScreenTimeout_; }
|
||||
bool markdown() const { return markdown_; }
|
||||
bool animateImagesOnHover() const { return animateImagesOnHover_; }
|
||||
bool typingNotifications() const { return typingNotifications_; }
|
||||
bool sortByImportance() const { return sortByImportance_; }
|
||||
bool buttonsInTimeline() const { return buttonsInTimeline_; }
|
||||
bool mobileMode() const { return mobileMode_; }
|
||||
bool readReceipts() const { return readReceipts_; }
|
||||
bool hasDesktopNotifications() const { return hasDesktopNotifications_; }
|
||||
bool hasAlertOnNotification() const { return hasAlertOnNotification_; }
|
||||
bool hasNotifications() const
|
||||
{
|
||||
return hasDesktopNotifications() || hasAlertOnNotification();
|
||||
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
|
||||
bool messageHoverHighlight() const { return messageHoverHighlight_; }
|
||||
bool enlargeEmojiOnlyMessages() const { return enlargeEmojiOnlyMessages_; }
|
||||
bool tray() const { return tray_; }
|
||||
bool startInTray() const { return startInTray_; }
|
||||
bool groupView() const { return groupView_; }
|
||||
bool avatarCircles() const { return avatarCircles_; }
|
||||
bool decryptSidebar() const { return decryptSidebar_; }
|
||||
bool privacyScreen() const { return privacyScreen_; }
|
||||
int privacyScreenTimeout() const { return privacyScreenTimeout_; }
|
||||
bool markdown() const { return markdown_; }
|
||||
bool animateImagesOnHover() const { return animateImagesOnHover_; }
|
||||
bool typingNotifications() const { return typingNotifications_; }
|
||||
bool sortByImportance() const { return sortByImportance_; }
|
||||
bool buttonsInTimeline() const { return buttonsInTimeline_; }
|
||||
bool mobileMode() const { return mobileMode_; }
|
||||
bool readReceipts() const { return readReceipts_; }
|
||||
bool hasDesktopNotifications() const { return hasDesktopNotifications_; }
|
||||
bool hasAlertOnNotification() const { return hasAlertOnNotification_; }
|
||||
bool hasNotifications() const { return hasDesktopNotifications() || hasAlertOnNotification(); }
|
||||
int timelineMaxWidth() const { return timelineMaxWidth_; }
|
||||
int communityListWidth() const { return communityListWidth_; }
|
||||
int roomListWidth() const { return roomListWidth_; }
|
||||
double fontSize() const { return baseFontSize_; }
|
||||
QString font() const { return font_; }
|
||||
QString emojiFont() const
|
||||
{
|
||||
if (emojiFont_ == "Default") {
|
||||
return tr("Default");
|
||||
}
|
||||
int timelineMaxWidth() const { return timelineMaxWidth_; }
|
||||
int communityListWidth() const { return communityListWidth_; }
|
||||
int roomListWidth() const { return roomListWidth_; }
|
||||
double fontSize() const { return baseFontSize_; }
|
||||
QString font() const { return font_; }
|
||||
QString emojiFont() const
|
||||
{
|
||||
if (emojiFont_ == "Default") {
|
||||
return tr("Default");
|
||||
}
|
||||
|
||||
return emojiFont_;
|
||||
}
|
||||
Presence presence() const { return presence_; }
|
||||
QString ringtone() const { return ringtone_; }
|
||||
QString microphone() const { return microphone_; }
|
||||
QString camera() const { return camera_; }
|
||||
QString cameraResolution() const { return cameraResolution_; }
|
||||
QString cameraFrameRate() const { return cameraFrameRate_; }
|
||||
int screenShareFrameRate() const { return screenShareFrameRate_; }
|
||||
bool screenSharePiP() const { return screenSharePiP_; }
|
||||
bool screenShareRemoteVideo() const { return screenShareRemoteVideo_; }
|
||||
bool screenShareHideCursor() const { return screenShareHideCursor_; }
|
||||
bool useStunServer() const { return useStunServer_; }
|
||||
bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; }
|
||||
bool onlyShareKeysWithVerifiedUsers() const { return onlyShareKeysWithVerifiedUsers_; }
|
||||
bool useOnlineKeyBackup() const { return useOnlineKeyBackup_; }
|
||||
QString profile() const { return profile_; }
|
||||
QString userId() const { return userId_; }
|
||||
QString accessToken() const { return accessToken_; }
|
||||
QString deviceId() const { return deviceId_; }
|
||||
QString homeserver() const { return homeserver_; }
|
||||
bool disableCertificateValidation() const { return disableCertificateValidation_; }
|
||||
QStringList hiddenTags() const { return hiddenTags_; }
|
||||
bool useIdenticon() const { return useIdenticon_ && JdenticonProvider::isAvailable(); }
|
||||
return emojiFont_;
|
||||
}
|
||||
Presence presence() const { return presence_; }
|
||||
QString ringtone() const { return ringtone_; }
|
||||
QString microphone() const { return microphone_; }
|
||||
QString camera() const { return camera_; }
|
||||
QString cameraResolution() const { return cameraResolution_; }
|
||||
QString cameraFrameRate() const { return cameraFrameRate_; }
|
||||
int screenShareFrameRate() const { return screenShareFrameRate_; }
|
||||
bool screenSharePiP() const { return screenSharePiP_; }
|
||||
bool screenShareRemoteVideo() const { return screenShareRemoteVideo_; }
|
||||
bool screenShareHideCursor() const { return screenShareHideCursor_; }
|
||||
bool useStunServer() const { return useStunServer_; }
|
||||
bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; }
|
||||
bool onlyShareKeysWithVerifiedUsers() const { return onlyShareKeysWithVerifiedUsers_; }
|
||||
bool useOnlineKeyBackup() const { return useOnlineKeyBackup_; }
|
||||
QString profile() const { return profile_; }
|
||||
QString userId() const { return userId_; }
|
||||
QString accessToken() const { return accessToken_; }
|
||||
QString deviceId() const { return deviceId_; }
|
||||
QString homeserver() const { return homeserver_; }
|
||||
bool disableCertificateValidation() const { return disableCertificateValidation_; }
|
||||
QStringList hiddenTags() const { return hiddenTags_; }
|
||||
bool useIdenticon() const { return useIdenticon_ && JdenticonProvider::isAvailable(); }
|
||||
|
||||
signals:
|
||||
void groupViewStateChanged(bool state);
|
||||
void roomSortingChanged(bool state);
|
||||
void themeChanged(QString state);
|
||||
void messageHoverHighlightChanged(bool state);
|
||||
void enlargeEmojiOnlyMessagesChanged(bool state);
|
||||
void trayChanged(bool state);
|
||||
void startInTrayChanged(bool state);
|
||||
void markdownChanged(bool state);
|
||||
void animateImagesOnHoverChanged(bool state);
|
||||
void typingNotificationsChanged(bool state);
|
||||
void buttonInTimelineChanged(bool state);
|
||||
void readReceiptsChanged(bool state);
|
||||
void desktopNotificationsChanged(bool state);
|
||||
void alertOnNotificationChanged(bool state);
|
||||
void avatarCirclesChanged(bool state);
|
||||
void decryptSidebarChanged(bool state);
|
||||
void privacyScreenChanged(bool state);
|
||||
void privacyScreenTimeoutChanged(int state);
|
||||
void timelineMaxWidthChanged(int state);
|
||||
void roomListWidthChanged(int state);
|
||||
void communityListWidthChanged(int state);
|
||||
void mobileModeChanged(bool mode);
|
||||
void fontSizeChanged(double state);
|
||||
void fontChanged(QString state);
|
||||
void emojiFontChanged(QString state);
|
||||
void presenceChanged(Presence state);
|
||||
void ringtoneChanged(QString ringtone);
|
||||
void microphoneChanged(QString microphone);
|
||||
void cameraChanged(QString camera);
|
||||
void cameraResolutionChanged(QString resolution);
|
||||
void cameraFrameRateChanged(QString frameRate);
|
||||
void screenShareFrameRateChanged(int frameRate);
|
||||
void screenSharePiPChanged(bool state);
|
||||
void screenShareRemoteVideoChanged(bool state);
|
||||
void screenShareHideCursorChanged(bool state);
|
||||
void useStunServerChanged(bool state);
|
||||
void onlyShareKeysWithVerifiedUsersChanged(bool state);
|
||||
void shareKeysWithTrustedUsersChanged(bool state);
|
||||
void useOnlineKeyBackupChanged(bool state);
|
||||
void profileChanged(QString profile);
|
||||
void userIdChanged(QString userId);
|
||||
void accessTokenChanged(QString accessToken);
|
||||
void deviceIdChanged(QString deviceId);
|
||||
void homeserverChanged(QString homeserver);
|
||||
void disableCertificateValidationChanged(bool disabled);
|
||||
void useIdenticonChanged(bool state);
|
||||
void groupViewStateChanged(bool state);
|
||||
void roomSortingChanged(bool state);
|
||||
void themeChanged(QString state);
|
||||
void messageHoverHighlightChanged(bool state);
|
||||
void enlargeEmojiOnlyMessagesChanged(bool state);
|
||||
void trayChanged(bool state);
|
||||
void startInTrayChanged(bool state);
|
||||
void markdownChanged(bool state);
|
||||
void animateImagesOnHoverChanged(bool state);
|
||||
void typingNotificationsChanged(bool state);
|
||||
void buttonInTimelineChanged(bool state);
|
||||
void readReceiptsChanged(bool state);
|
||||
void desktopNotificationsChanged(bool state);
|
||||
void alertOnNotificationChanged(bool state);
|
||||
void avatarCirclesChanged(bool state);
|
||||
void decryptSidebarChanged(bool state);
|
||||
void privacyScreenChanged(bool state);
|
||||
void privacyScreenTimeoutChanged(int state);
|
||||
void timelineMaxWidthChanged(int state);
|
||||
void roomListWidthChanged(int state);
|
||||
void communityListWidthChanged(int state);
|
||||
void mobileModeChanged(bool mode);
|
||||
void fontSizeChanged(double state);
|
||||
void fontChanged(QString state);
|
||||
void emojiFontChanged(QString state);
|
||||
void presenceChanged(Presence state);
|
||||
void ringtoneChanged(QString ringtone);
|
||||
void microphoneChanged(QString microphone);
|
||||
void cameraChanged(QString camera);
|
||||
void cameraResolutionChanged(QString resolution);
|
||||
void cameraFrameRateChanged(QString frameRate);
|
||||
void screenShareFrameRateChanged(int frameRate);
|
||||
void screenSharePiPChanged(bool state);
|
||||
void screenShareRemoteVideoChanged(bool state);
|
||||
void screenShareHideCursorChanged(bool state);
|
||||
void useStunServerChanged(bool state);
|
||||
void onlyShareKeysWithVerifiedUsersChanged(bool state);
|
||||
void shareKeysWithTrustedUsersChanged(bool state);
|
||||
void useOnlineKeyBackupChanged(bool state);
|
||||
void profileChanged(QString profile);
|
||||
void userIdChanged(QString userId);
|
||||
void accessTokenChanged(QString accessToken);
|
||||
void deviceIdChanged(QString deviceId);
|
||||
void homeserverChanged(QString homeserver);
|
||||
void disableCertificateValidationChanged(bool disabled);
|
||||
void useIdenticonChanged(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 messageHoverHighlight_;
|
||||
bool enlargeEmojiOnlyMessages_;
|
||||
bool tray_;
|
||||
bool startInTray_;
|
||||
bool groupView_;
|
||||
bool markdown_;
|
||||
bool animateImagesOnHover_;
|
||||
bool typingNotifications_;
|
||||
bool sortByImportance_;
|
||||
bool buttonsInTimeline_;
|
||||
bool readReceipts_;
|
||||
bool hasDesktopNotifications_;
|
||||
bool hasAlertOnNotification_;
|
||||
bool avatarCircles_;
|
||||
bool decryptSidebar_;
|
||||
bool privacyScreen_;
|
||||
int privacyScreenTimeout_;
|
||||
bool shareKeysWithTrustedUsers_;
|
||||
bool onlyShareKeysWithVerifiedUsers_;
|
||||
bool useOnlineKeyBackup_;
|
||||
bool mobileMode_;
|
||||
int timelineMaxWidth_;
|
||||
int roomListWidth_;
|
||||
int communityListWidth_;
|
||||
double baseFontSize_;
|
||||
QString font_;
|
||||
QString emojiFont_;
|
||||
Presence presence_;
|
||||
QString ringtone_;
|
||||
QString microphone_;
|
||||
QString camera_;
|
||||
QString cameraResolution_;
|
||||
QString cameraFrameRate_;
|
||||
int screenShareFrameRate_;
|
||||
bool screenSharePiP_;
|
||||
bool screenShareRemoteVideo_;
|
||||
bool screenShareHideCursor_;
|
||||
bool useStunServer_;
|
||||
bool disableCertificateValidation_ = false;
|
||||
QString profile_;
|
||||
QString userId_;
|
||||
QString accessToken_;
|
||||
QString deviceId_;
|
||||
QString homeserver_;
|
||||
QStringList hiddenTags_;
|
||||
bool useIdenticon_;
|
||||
// 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 messageHoverHighlight_;
|
||||
bool enlargeEmojiOnlyMessages_;
|
||||
bool tray_;
|
||||
bool startInTray_;
|
||||
bool groupView_;
|
||||
bool markdown_;
|
||||
bool animateImagesOnHover_;
|
||||
bool typingNotifications_;
|
||||
bool sortByImportance_;
|
||||
bool buttonsInTimeline_;
|
||||
bool readReceipts_;
|
||||
bool hasDesktopNotifications_;
|
||||
bool hasAlertOnNotification_;
|
||||
bool avatarCircles_;
|
||||
bool decryptSidebar_;
|
||||
bool privacyScreen_;
|
||||
int privacyScreenTimeout_;
|
||||
bool shareKeysWithTrustedUsers_;
|
||||
bool onlyShareKeysWithVerifiedUsers_;
|
||||
bool useOnlineKeyBackup_;
|
||||
bool mobileMode_;
|
||||
int timelineMaxWidth_;
|
||||
int roomListWidth_;
|
||||
int communityListWidth_;
|
||||
double baseFontSize_;
|
||||
QString font_;
|
||||
QString emojiFont_;
|
||||
Presence presence_;
|
||||
QString ringtone_;
|
||||
QString microphone_;
|
||||
QString camera_;
|
||||
QString cameraResolution_;
|
||||
QString cameraFrameRate_;
|
||||
int screenShareFrameRate_;
|
||||
bool screenSharePiP_;
|
||||
bool screenShareRemoteVideo_;
|
||||
bool screenShareHideCursor_;
|
||||
bool useStunServer_;
|
||||
bool disableCertificateValidation_ = false;
|
||||
QString profile_;
|
||||
QString userId_;
|
||||
QString accessToken_;
|
||||
QString deviceId_;
|
||||
QString homeserver_;
|
||||
QStringList hiddenTags_;
|
||||
bool useIdenticon_;
|
||||
|
||||
QSettings settings;
|
||||
QSettings settings;
|
||||
|
||||
static QSharedPointer<UserSettings> instance_;
|
||||
static QSharedPointer<UserSettings> instance_;
|
||||
};
|
||||
|
||||
class HorizontalLine : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
HorizontalLine(QWidget *parent = nullptr);
|
||||
HorizontalLine(QWidget *parent = nullptr);
|
||||
};
|
||||
|
||||
class UserSettingsPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
UserSettingsPage(QSharedPointer<UserSettings> settings, QWidget *parent = nullptr);
|
||||
UserSettingsPage(QSharedPointer<UserSettings> settings, QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
signals:
|
||||
void moveBack();
|
||||
void trayOptionChanged(bool value);
|
||||
void themeChanged();
|
||||
void decryptSidebarChanged();
|
||||
void moveBack();
|
||||
void trayOptionChanged(bool value);
|
||||
void themeChanged();
|
||||
void decryptSidebarChanged();
|
||||
|
||||
public slots:
|
||||
void updateSecretStatus();
|
||||
void updateSecretStatus();
|
||||
|
||||
private slots:
|
||||
void importSessionKeys();
|
||||
void exportSessionKeys();
|
||||
void importSessionKeys();
|
||||
void exportSessionKeys();
|
||||
|
||||
private:
|
||||
// Layouts
|
||||
QVBoxLayout *topLayout_;
|
||||
QHBoxLayout *topBarLayout_;
|
||||
QFormLayout *formLayout_;
|
||||
// Layouts
|
||||
QVBoxLayout *topLayout_;
|
||||
QHBoxLayout *topBarLayout_;
|
||||
QFormLayout *formLayout_;
|
||||
|
||||
// Shared settings object.
|
||||
QSharedPointer<UserSettings> settings_;
|
||||
// Shared settings object.
|
||||
QSharedPointer<UserSettings> settings_;
|
||||
|
||||
Toggle *trayToggle_;
|
||||
Toggle *startInTrayToggle_;
|
||||
Toggle *groupViewToggle_;
|
||||
Toggle *timelineButtonsToggle_;
|
||||
Toggle *typingNotifications_;
|
||||
Toggle *messageHoverHighlight_;
|
||||
Toggle *enlargeEmojiOnlyMessages_;
|
||||
Toggle *sortByImportance_;
|
||||
Toggle *readReceipts_;
|
||||
Toggle *markdown_;
|
||||
Toggle *animateImagesOnHover_;
|
||||
Toggle *desktopNotifications_;
|
||||
Toggle *alertOnNotification_;
|
||||
Toggle *avatarCircles_;
|
||||
Toggle *useIdenticon_;
|
||||
Toggle *useStunServer_;
|
||||
Toggle *decryptSidebar_;
|
||||
Toggle *privacyScreen_;
|
||||
QSpinBox *privacyScreenTimeout_;
|
||||
Toggle *shareKeysWithTrustedUsers_;
|
||||
Toggle *onlyShareKeysWithVerifiedUsers_;
|
||||
Toggle *useOnlineKeyBackup_;
|
||||
Toggle *mobileMode_;
|
||||
QLabel *deviceFingerprintValue_;
|
||||
QLabel *deviceIdValue_;
|
||||
QLabel *backupSecretCached;
|
||||
QLabel *masterSecretCached;
|
||||
QLabel *selfSigningSecretCached;
|
||||
QLabel *userSigningSecretCached;
|
||||
Toggle *trayToggle_;
|
||||
Toggle *startInTrayToggle_;
|
||||
Toggle *groupViewToggle_;
|
||||
Toggle *timelineButtonsToggle_;
|
||||
Toggle *typingNotifications_;
|
||||
Toggle *messageHoverHighlight_;
|
||||
Toggle *enlargeEmojiOnlyMessages_;
|
||||
Toggle *sortByImportance_;
|
||||
Toggle *readReceipts_;
|
||||
Toggle *markdown_;
|
||||
Toggle *animateImagesOnHover_;
|
||||
Toggle *desktopNotifications_;
|
||||
Toggle *alertOnNotification_;
|
||||
Toggle *avatarCircles_;
|
||||
Toggle *useIdenticon_;
|
||||
Toggle *useStunServer_;
|
||||
Toggle *decryptSidebar_;
|
||||
Toggle *privacyScreen_;
|
||||
QSpinBox *privacyScreenTimeout_;
|
||||
Toggle *shareKeysWithTrustedUsers_;
|
||||
Toggle *onlyShareKeysWithVerifiedUsers_;
|
||||
Toggle *useOnlineKeyBackup_;
|
||||
Toggle *mobileMode_;
|
||||
QLabel *deviceFingerprintValue_;
|
||||
QLabel *deviceIdValue_;
|
||||
QLabel *backupSecretCached;
|
||||
QLabel *masterSecretCached;
|
||||
QLabel *selfSigningSecretCached;
|
||||
QLabel *userSigningSecretCached;
|
||||
|
||||
QComboBox *themeCombo_;
|
||||
QComboBox *scaleFactorCombo_;
|
||||
QComboBox *fontSizeCombo_;
|
||||
QFontComboBox *fontSelectionCombo_;
|
||||
QComboBox *emojiFontSelectionCombo_;
|
||||
QComboBox *ringtoneCombo_;
|
||||
QComboBox *microphoneCombo_;
|
||||
QComboBox *cameraCombo_;
|
||||
QComboBox *cameraResolutionCombo_;
|
||||
QComboBox *cameraFrameRateCombo_;
|
||||
QComboBox *themeCombo_;
|
||||
QComboBox *scaleFactorCombo_;
|
||||
QComboBox *fontSizeCombo_;
|
||||
QFontComboBox *fontSelectionCombo_;
|
||||
QComboBox *emojiFontSelectionCombo_;
|
||||
QComboBox *ringtoneCombo_;
|
||||
QComboBox *microphoneCombo_;
|
||||
QComboBox *cameraCombo_;
|
||||
QComboBox *cameraResolutionCombo_;
|
||||
QComboBox *cameraFrameRateCombo_;
|
||||
|
||||
QSpinBox *timelineMaxWidthSpin_;
|
||||
QSpinBox *timelineMaxWidthSpin_;
|
||||
|
||||
int sideMargin_ = 0;
|
||||
int sideMargin_ = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,51 +14,51 @@ UsersModel::UsersModel(const std::string &roomId, QObject *parent)
|
|||
: QAbstractListModel(parent)
|
||||
, room_id(roomId)
|
||||
{
|
||||
roomMembers_ = cache::roomMembers(roomId);
|
||||
for (const auto &m : roomMembers_) {
|
||||
displayNames.push_back(QString::fromStdString(cache::displayName(room_id, m)));
|
||||
userids.push_back(QString::fromStdString(m));
|
||||
}
|
||||
roomMembers_ = cache::roomMembers(roomId);
|
||||
for (const auto &m : roomMembers_) {
|
||||
displayNames.push_back(QString::fromStdString(cache::displayName(room_id, m)));
|
||||
userids.push_back(QString::fromStdString(m));
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
UsersModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{CompletionModel::CompletionRole, "completionRole"},
|
||||
{CompletionModel::SearchRole, "searchRole"},
|
||||
{CompletionModel::SearchRole2, "searchRole2"},
|
||||
{Roles::DisplayName, "displayName"},
|
||||
{Roles::AvatarUrl, "avatarUrl"},
|
||||
{Roles::UserID, "userid"},
|
||||
};
|
||||
return {
|
||||
{CompletionModel::CompletionRole, "completionRole"},
|
||||
{CompletionModel::SearchRole, "searchRole"},
|
||||
{CompletionModel::SearchRole2, "searchRole2"},
|
||||
{Roles::DisplayName, "displayName"},
|
||||
{Roles::AvatarUrl, "avatarUrl"},
|
||||
{Roles::UserID, "userid"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
UsersModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
switch (role) {
|
||||
case CompletionModel::CompletionRole:
|
||||
if (UserSettings::instance()->markdown())
|
||||
return QString("[%1](https://matrix.to/#/%2)")
|
||||
.arg(displayNames[index.row()].toHtmlEscaped())
|
||||
.arg(QString(QUrl::toPercentEncoding(userids[index.row()])));
|
||||
else
|
||||
return displayNames[index.row()];
|
||||
case CompletionModel::SearchRole:
|
||||
return displayNames[index.row()];
|
||||
case Qt::DisplayRole:
|
||||
case Roles::DisplayName:
|
||||
return displayNames[index.row()].toHtmlEscaped();
|
||||
case CompletionModel::SearchRole2:
|
||||
return userids[index.row()];
|
||||
case Roles::AvatarUrl:
|
||||
return cache::avatarUrl(QString::fromStdString(room_id),
|
||||
QString::fromStdString(roomMembers_[index.row()]));
|
||||
case Roles::UserID:
|
||||
return userids[index.row()].toHtmlEscaped();
|
||||
}
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
switch (role) {
|
||||
case CompletionModel::CompletionRole:
|
||||
if (UserSettings::instance()->markdown())
|
||||
return QString("[%1](https://matrix.to/#/%2)")
|
||||
.arg(displayNames[index.row()].toHtmlEscaped())
|
||||
.arg(QString(QUrl::toPercentEncoding(userids[index.row()])));
|
||||
else
|
||||
return displayNames[index.row()];
|
||||
case CompletionModel::SearchRole:
|
||||
return displayNames[index.row()];
|
||||
case Qt::DisplayRole:
|
||||
case Roles::DisplayName:
|
||||
return displayNames[index.row()].toHtmlEscaped();
|
||||
case CompletionModel::SearchRole2:
|
||||
return userids[index.row()];
|
||||
case Roles::AvatarUrl:
|
||||
return cache::avatarUrl(QString::fromStdString(room_id),
|
||||
QString::fromStdString(roomMembers_[index.row()]));
|
||||
case Roles::UserID:
|
||||
return userids[index.row()].toHtmlEscaped();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,25 +9,25 @@
|
|||
class UsersModel : public QAbstractListModel
|
||||
{
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
AvatarUrl = Qt::UserRole,
|
||||
DisplayName,
|
||||
UserID,
|
||||
};
|
||||
enum Roles
|
||||
{
|
||||
AvatarUrl = Qt::UserRole,
|
||||
DisplayName,
|
||||
UserID,
|
||||
};
|
||||
|
||||
UsersModel(const std::string &roomId, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
(void)parent;
|
||||
return (int)roomMembers_.size();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
UsersModel(const std::string &roomId, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
(void)parent;
|
||||
return (int)roomMembers_.size();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
private:
|
||||
std::string room_id;
|
||||
std::vector<std::string> roomMembers_;
|
||||
std::vector<QString> displayNames;
|
||||
std::vector<QString> userids;
|
||||
std::string room_id;
|
||||
std::vector<std::string> roomMembers_;
|
||||
std::vector<QString> displayNames;
|
||||
std::vector<QString> userids;
|
||||
};
|
||||
|
|
|
|||
1067
src/Utils.cpp
1067
src/Utils.cpp
File diff suppressed because it is too large
Load diff
230
src/Utils.h
230
src/Utils.h
|
|
@ -29,12 +29,12 @@ class QComboBox;
|
|||
// outgoing messages
|
||||
struct RelatedInfo
|
||||
{
|
||||
using MsgType = mtx::events::MessageType;
|
||||
MsgType type;
|
||||
QString room;
|
||||
QString quoted_body, quoted_formatted_body;
|
||||
std::string related_event;
|
||||
QString quoted_user;
|
||||
using MsgType = mtx::events::MessageType;
|
||||
MsgType type;
|
||||
QString room;
|
||||
QString quoted_body, quoted_formatted_body;
|
||||
std::string related_event;
|
||||
QString quoted_user;
|
||||
};
|
||||
|
||||
namespace utils {
|
||||
|
|
@ -97,112 +97,96 @@ 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>;
|
||||
using File = mtx::events::RoomEvent<mtx::events::msg::File>;
|
||||
using Image = mtx::events::RoomEvent<mtx::events::msg::Image>;
|
||||
using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>;
|
||||
using Sticker = mtx::events::Sticker;
|
||||
using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
|
||||
using Video = mtx::events::RoomEvent<mtx::events::msg::Video>;
|
||||
using CallInvite = mtx::events::RoomEvent<mtx::events::msg::CallInvite>;
|
||||
using CallAnswer = mtx::events::RoomEvent<mtx::events::msg::CallAnswer>;
|
||||
using CallHangUp = mtx::events::RoomEvent<mtx::events::msg::CallHangUp>;
|
||||
using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
|
||||
using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>;
|
||||
using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
|
||||
using File = mtx::events::RoomEvent<mtx::events::msg::File>;
|
||||
using Image = mtx::events::RoomEvent<mtx::events::msg::Image>;
|
||||
using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>;
|
||||
using Sticker = mtx::events::Sticker;
|
||||
using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
|
||||
using Video = mtx::events::RoomEvent<mtx::events::msg::Video>;
|
||||
using CallInvite = mtx::events::RoomEvent<mtx::events::msg::CallInvite>;
|
||||
using CallAnswer = mtx::events::RoomEvent<mtx::events::msg::CallAnswer>;
|
||||
using CallHangUp = mtx::events::RoomEvent<mtx::events::msg::CallHangUp>;
|
||||
using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
|
||||
|
||||
if (std::is_same<T, Audio>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent an audio clip");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent an audio clip")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Image>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent an image");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent an image")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, File>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent a file");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent a file")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Video>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent a video");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent a video")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Sticker>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent a sticker");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent a sticker")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Notice>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent a notification");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent a notification")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Text>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:", "You: %1")
|
||||
.arg(body);
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:", "%1: %2")
|
||||
.arg(username)
|
||||
.arg(body);
|
||||
} else if (std::is_same<T, Emote>::value) {
|
||||
return QString("* %1 %2").arg(username).arg(body);
|
||||
} else if (std::is_same<T, Encrypted>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent an encrypted message");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent an encrypted message")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, CallInvite>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You placed a call");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 placed a call")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, CallAnswer>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You answered a call");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 answered a call")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, CallHangUp>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You ended a call");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 ended a call")
|
||||
.arg(username);
|
||||
} else {
|
||||
return QCoreApplication::translate("utils", "Unknown Message Type");
|
||||
}
|
||||
if (std::is_same<T, Audio>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent an audio clip");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:", "%1 sent an audio clip")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Image>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:", "You sent an image");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:", "%1 sent an image")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, File>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:", "You sent a file");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:", "%1 sent a file")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Video>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:", "You sent a video");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:", "%1 sent a video")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Sticker>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:", "You sent a sticker");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:", "%1 sent a sticker")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Notice>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent a notification");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent a notification")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Text>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:", "You: %1").arg(body);
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:", "%1: %2")
|
||||
.arg(username)
|
||||
.arg(body);
|
||||
} else if (std::is_same<T, Emote>::value) {
|
||||
return QString("* %1 %2").arg(username).arg(body);
|
||||
} else if (std::is_same<T, Encrypted>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent an encrypted message");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent an encrypted message")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, CallInvite>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:", "You placed a call");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:", "%1 placed a call")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, CallAnswer>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:", "You answered a call");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:", "%1 answered a call")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, CallHangUp>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:", "You ended a call");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:", "%1 ended a call")
|
||||
.arg(username);
|
||||
} else {
|
||||
return QCoreApplication::translate("utils", "Unknown Message Type");
|
||||
}
|
||||
}
|
||||
|
||||
//! Scale down an image to fit to the given width & height limitations.
|
||||
|
|
@ -214,19 +198,19 @@ template<typename ContainerT, typename PredicateT>
|
|||
void
|
||||
erase_if(ContainerT &items, const PredicateT &predicate)
|
||||
{
|
||||
for (auto it = items.begin(); it != items.end();) {
|
||||
if (predicate(*it))
|
||||
it = items.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
for (auto it = items.begin(); it != items.end();) {
|
||||
if (predicate(*it))
|
||||
it = items.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
QString
|
||||
message_body(const mtx::events::collections::TimelineEvents &event)
|
||||
{
|
||||
return QString::fromStdString(std::get<T>(event).content.body);
|
||||
return QString::fromStdString(std::get<T>(event).content.body);
|
||||
}
|
||||
|
||||
//! Calculate the Levenshtein distance between two strings with character skipping.
|
||||
|
|
@ -253,13 +237,13 @@ template<typename RoomMessageT>
|
|||
QString
|
||||
getMessageBody(const RoomMessageT &event)
|
||||
{
|
||||
if (event.content.format.empty())
|
||||
return QString::fromStdString(event.content.body).toHtmlEscaped();
|
||||
if (event.content.format.empty())
|
||||
return QString::fromStdString(event.content.body).toHtmlEscaped();
|
||||
|
||||
if (event.content.format != mtx::common::FORMAT_MSG_TYPE)
|
||||
return QString::fromStdString(event.content.body).toHtmlEscaped();
|
||||
if (event.content.format != mtx::common::FORMAT_MSG_TYPE)
|
||||
return QString::fromStdString(event.content.body).toHtmlEscaped();
|
||||
|
||||
return QString::fromStdString(event.content.formatted_body);
|
||||
return QString::fromStdString(event.content.formatted_body);
|
||||
}
|
||||
|
||||
//! Replace raw URLs in text with HTML link tags.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -20,22 +20,22 @@ Q_NAMESPACE
|
|||
|
||||
enum class CallType
|
||||
{
|
||||
VOICE,
|
||||
VIDEO,
|
||||
SCREEN // localUser is sharing screen
|
||||
VOICE,
|
||||
VIDEO,
|
||||
SCREEN // localUser is sharing screen
|
||||
};
|
||||
Q_ENUM_NS(CallType)
|
||||
|
||||
enum class State
|
||||
{
|
||||
DISCONNECTED,
|
||||
ICEFAILED,
|
||||
INITIATING,
|
||||
INITIATED,
|
||||
OFFERSENT,
|
||||
ANSWERSENT,
|
||||
CONNECTING,
|
||||
CONNECTED
|
||||
DISCONNECTED,
|
||||
ICEFAILED,
|
||||
INITIATING,
|
||||
INITIATED,
|
||||
OFFERSENT,
|
||||
ANSWERSENT,
|
||||
CONNECTING,
|
||||
CONNECTED
|
||||
|
||||
};
|
||||
Q_ENUM_NS(State)
|
||||
|
|
@ -43,75 +43,75 @@ Q_ENUM_NS(State)
|
|||
|
||||
class WebRTCSession : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static WebRTCSession &instance()
|
||||
{
|
||||
static WebRTCSession instance;
|
||||
return instance;
|
||||
}
|
||||
static WebRTCSession &instance()
|
||||
{
|
||||
static WebRTCSession instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool havePlugins(bool isVideo, std::string *errorMessage = nullptr);
|
||||
webrtc::CallType callType() const { return callType_; }
|
||||
webrtc::State state() const { return state_; }
|
||||
bool haveLocalPiP() const;
|
||||
bool isOffering() const { return isOffering_; }
|
||||
bool isRemoteVideoRecvOnly() const { return isRemoteVideoRecvOnly_; }
|
||||
bool isRemoteVideoSendOnly() const { return isRemoteVideoSendOnly_; }
|
||||
bool havePlugins(bool isVideo, std::string *errorMessage = nullptr);
|
||||
webrtc::CallType callType() const { return callType_; }
|
||||
webrtc::State state() const { return state_; }
|
||||
bool haveLocalPiP() const;
|
||||
bool isOffering() const { return isOffering_; }
|
||||
bool isRemoteVideoRecvOnly() const { return isRemoteVideoRecvOnly_; }
|
||||
bool isRemoteVideoSendOnly() const { return isRemoteVideoSendOnly_; }
|
||||
|
||||
bool createOffer(webrtc::CallType, uint32_t shareWindowId);
|
||||
bool acceptOffer(const std::string &sdp);
|
||||
bool acceptAnswer(const std::string &sdp);
|
||||
void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
|
||||
bool createOffer(webrtc::CallType, uint32_t shareWindowId);
|
||||
bool acceptOffer(const std::string &sdp);
|
||||
bool acceptAnswer(const std::string &sdp);
|
||||
void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
|
||||
|
||||
bool isMicMuted() const;
|
||||
bool toggleMicMute();
|
||||
void toggleLocalPiP();
|
||||
void end();
|
||||
bool isMicMuted() const;
|
||||
bool toggleMicMute();
|
||||
void toggleLocalPiP();
|
||||
void end();
|
||||
|
||||
void setTurnServers(const std::vector<std::string> &uris) { turnServers_ = uris; }
|
||||
void setTurnServers(const std::vector<std::string> &uris) { turnServers_ = uris; }
|
||||
|
||||
void setVideoItem(QQuickItem *item) { videoItem_ = item; }
|
||||
QQuickItem *getVideoItem() const { return videoItem_; }
|
||||
void setVideoItem(QQuickItem *item) { videoItem_ = item; }
|
||||
QQuickItem *getVideoItem() const { return videoItem_; }
|
||||
|
||||
signals:
|
||||
void offerCreated(const std::string &sdp,
|
||||
const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
|
||||
void answerCreated(const std::string &sdp,
|
||||
const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
|
||||
void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &);
|
||||
void stateChanged(webrtc::State);
|
||||
void offerCreated(const std::string &sdp,
|
||||
const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
|
||||
void answerCreated(const std::string &sdp,
|
||||
const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
|
||||
void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &);
|
||||
void stateChanged(webrtc::State);
|
||||
|
||||
private slots:
|
||||
void setState(webrtc::State state) { state_ = state; }
|
||||
void setState(webrtc::State state) { state_ = state; }
|
||||
|
||||
private:
|
||||
WebRTCSession();
|
||||
WebRTCSession();
|
||||
|
||||
CallDevices &devices_;
|
||||
bool initialised_ = false;
|
||||
bool haveVoicePlugins_ = false;
|
||||
bool haveVideoPlugins_ = false;
|
||||
webrtc::CallType callType_ = webrtc::CallType::VOICE;
|
||||
webrtc::State state_ = webrtc::State::DISCONNECTED;
|
||||
bool isOffering_ = false;
|
||||
bool isRemoteVideoRecvOnly_ = false;
|
||||
bool isRemoteVideoSendOnly_ = false;
|
||||
QQuickItem *videoItem_ = nullptr;
|
||||
GstElement *pipe_ = nullptr;
|
||||
GstElement *webrtc_ = nullptr;
|
||||
unsigned int busWatchId_ = 0;
|
||||
std::vector<std::string> turnServers_;
|
||||
uint32_t shareWindowId_ = 0;
|
||||
CallDevices &devices_;
|
||||
bool initialised_ = false;
|
||||
bool haveVoicePlugins_ = false;
|
||||
bool haveVideoPlugins_ = false;
|
||||
webrtc::CallType callType_ = webrtc::CallType::VOICE;
|
||||
webrtc::State state_ = webrtc::State::DISCONNECTED;
|
||||
bool isOffering_ = false;
|
||||
bool isRemoteVideoRecvOnly_ = false;
|
||||
bool isRemoteVideoSendOnly_ = false;
|
||||
QQuickItem *videoItem_ = nullptr;
|
||||
GstElement *pipe_ = nullptr;
|
||||
GstElement *webrtc_ = nullptr;
|
||||
unsigned int busWatchId_ = 0;
|
||||
std::vector<std::string> turnServers_;
|
||||
uint32_t shareWindowId_ = 0;
|
||||
|
||||
bool init(std::string *errorMessage = nullptr);
|
||||
bool startPipeline(int opusPayloadType, int vp8PayloadType);
|
||||
bool createPipeline(int opusPayloadType, int vp8PayloadType);
|
||||
bool addVideoPipeline(int vp8PayloadType);
|
||||
void clear();
|
||||
bool init(std::string *errorMessage = nullptr);
|
||||
bool startPipeline(int opusPayloadType, int vp8PayloadType);
|
||||
bool createPipeline(int opusPayloadType, int vp8PayloadType);
|
||||
bool addVideoPipeline(int vp8PayloadType);
|
||||
void clear();
|
||||
|
||||
public:
|
||||
WebRTCSession(WebRTCSession const &) = delete;
|
||||
void operator=(WebRTCSession const &) = delete;
|
||||
WebRTCSession(WebRTCSession const &) = delete;
|
||||
void operator=(WebRTCSession const &) = delete;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,72 +16,72 @@
|
|||
WelcomePage::WelcomePage(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
auto topLayout_ = new QVBoxLayout(this);
|
||||
topLayout_->setSpacing(20);
|
||||
topLayout_->setAlignment(Qt::AlignCenter);
|
||||
auto topLayout_ = new QVBoxLayout(this);
|
||||
topLayout_->setSpacing(20);
|
||||
topLayout_->setAlignment(Qt::AlignCenter);
|
||||
|
||||
QFont headingFont;
|
||||
headingFont.setPointSizeF(headingFont.pointSizeF() * 2);
|
||||
QFont subTitleFont;
|
||||
subTitleFont.setPointSizeF(subTitleFont.pointSizeF() * 1.5);
|
||||
QFont headingFont;
|
||||
headingFont.setPointSizeF(headingFont.pointSizeF() * 2);
|
||||
QFont subTitleFont;
|
||||
subTitleFont.setPointSizeF(subTitleFont.pointSizeF() * 1.5);
|
||||
|
||||
QIcon icon{QIcon::fromTheme("nheko", QIcon{":/logos/splash.png"})};
|
||||
QIcon icon{QIcon::fromTheme("nheko", QIcon{":/logos/splash.png"})};
|
||||
|
||||
auto logo_ = new QLabel(this);
|
||||
logo_->setPixmap(icon.pixmap(256));
|
||||
logo_->setAlignment(Qt::AlignCenter);
|
||||
auto logo_ = new QLabel(this);
|
||||
logo_->setPixmap(icon.pixmap(256));
|
||||
logo_->setAlignment(Qt::AlignCenter);
|
||||
|
||||
QString heading(tr("Welcome to nheko! The desktop client for the Matrix protocol."));
|
||||
QString main(tr("Enjoy your stay!"));
|
||||
QString heading(tr("Welcome to nheko! The desktop client for the Matrix protocol."));
|
||||
QString main(tr("Enjoy your stay!"));
|
||||
|
||||
auto intoTxt_ = new TextLabel(heading, this);
|
||||
intoTxt_->setFont(headingFont);
|
||||
intoTxt_->setAlignment(Qt::AlignCenter);
|
||||
auto intoTxt_ = new TextLabel(heading, this);
|
||||
intoTxt_->setFont(headingFont);
|
||||
intoTxt_->setAlignment(Qt::AlignCenter);
|
||||
|
||||
auto subTitle = new TextLabel(main, this);
|
||||
subTitle->setFont(subTitleFont);
|
||||
subTitle->setAlignment(Qt::AlignCenter);
|
||||
auto subTitle = new TextLabel(main, this);
|
||||
subTitle->setFont(subTitleFont);
|
||||
subTitle->setAlignment(Qt::AlignCenter);
|
||||
|
||||
topLayout_->addStretch(1);
|
||||
topLayout_->addWidget(logo_);
|
||||
topLayout_->addWidget(intoTxt_);
|
||||
topLayout_->addWidget(subTitle);
|
||||
topLayout_->addStretch(1);
|
||||
topLayout_->addWidget(logo_);
|
||||
topLayout_->addWidget(intoTxt_);
|
||||
topLayout_->addWidget(subTitle);
|
||||
|
||||
auto btnLayout_ = new QHBoxLayout();
|
||||
btnLayout_->setSpacing(20);
|
||||
btnLayout_->setContentsMargins(0, 20, 0, 20);
|
||||
auto btnLayout_ = new QHBoxLayout();
|
||||
btnLayout_->setSpacing(20);
|
||||
btnLayout_->setContentsMargins(0, 20, 0, 20);
|
||||
|
||||
const int fontHeight = QFontMetrics{subTitleFont}.height();
|
||||
const int buttonHeight = fontHeight * 2.5;
|
||||
const int buttonWidth = fontHeight * 8;
|
||||
const int fontHeight = QFontMetrics{subTitleFont}.height();
|
||||
const int buttonHeight = fontHeight * 2.5;
|
||||
const int buttonWidth = fontHeight * 8;
|
||||
|
||||
auto registerBtn = new RaisedButton(tr("REGISTER"), this);
|
||||
registerBtn->setMinimumSize(buttonWidth, buttonHeight);
|
||||
registerBtn->setFontSize(subTitleFont.pointSizeF());
|
||||
registerBtn->setCornerRadius(conf::btn::cornerRadius);
|
||||
auto registerBtn = new RaisedButton(tr("REGISTER"), this);
|
||||
registerBtn->setMinimumSize(buttonWidth, buttonHeight);
|
||||
registerBtn->setFontSize(subTitleFont.pointSizeF());
|
||||
registerBtn->setCornerRadius(conf::btn::cornerRadius);
|
||||
|
||||
auto loginBtn = new RaisedButton(tr("LOGIN"), this);
|
||||
loginBtn->setMinimumSize(buttonWidth, buttonHeight);
|
||||
loginBtn->setFontSize(subTitleFont.pointSizeF());
|
||||
loginBtn->setCornerRadius(conf::btn::cornerRadius);
|
||||
auto loginBtn = new RaisedButton(tr("LOGIN"), this);
|
||||
loginBtn->setMinimumSize(buttonWidth, buttonHeight);
|
||||
loginBtn->setFontSize(subTitleFont.pointSizeF());
|
||||
loginBtn->setCornerRadius(conf::btn::cornerRadius);
|
||||
|
||||
btnLayout_->addStretch(1);
|
||||
btnLayout_->addWidget(registerBtn);
|
||||
btnLayout_->addWidget(loginBtn);
|
||||
btnLayout_->addStretch(1);
|
||||
btnLayout_->addStretch(1);
|
||||
btnLayout_->addWidget(registerBtn);
|
||||
btnLayout_->addWidget(loginBtn);
|
||||
btnLayout_->addStretch(1);
|
||||
|
||||
topLayout_->addLayout(btnLayout_);
|
||||
topLayout_->addStretch(1);
|
||||
topLayout_->addLayout(btnLayout_);
|
||||
topLayout_->addStretch(1);
|
||||
|
||||
connect(registerBtn, &QPushButton::clicked, this, &WelcomePage::userRegister);
|
||||
connect(loginBtn, &QPushButton::clicked, this, &WelcomePage::userLogin);
|
||||
connect(registerBtn, &QPushButton::clicked, this, &WelcomePage::userRegister);
|
||||
connect(loginBtn, &QPushButton::clicked, this, &WelcomePage::userLogin);
|
||||
}
|
||||
|
||||
void
|
||||
WelcomePage::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,18 +8,18 @@
|
|||
|
||||
class WelcomePage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WelcomePage(QWidget *parent = nullptr);
|
||||
explicit WelcomePage(QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
|
||||
signals:
|
||||
// Notify that the user wants to login in.
|
||||
void userLogin();
|
||||
// Notify that the user wants to login in.
|
||||
void userLogin();
|
||||
|
||||
// Notify that the user wants to register.
|
||||
void userRegister();
|
||||
// Notify that the user wants to register.
|
||||
void userRegister();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,142 +18,142 @@ using namespace dialogs;
|
|||
CreateRoom::CreateRoom(QWidget *parent)
|
||||
: QFrame(parent)
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
QFont largeFont;
|
||||
largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5);
|
||||
QFont largeFont;
|
||||
largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5);
|
||||
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
setMinimumHeight(conf::modals::MIN_WIDGET_HEIGHT);
|
||||
setMinimumWidth(conf::window::minModalWidth);
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
setMinimumHeight(conf::modals::MIN_WIDGET_HEIGHT);
|
||||
setMinimumWidth(conf::window::minModalWidth);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(15);
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(15);
|
||||
|
||||
confirmBtn_ = new QPushButton(tr("Create room"), this);
|
||||
confirmBtn_->setDefault(true);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
confirmBtn_ = new QPushButton(tr("Create room"), this);
|
||||
confirmBtn_->setDefault(true);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
|
||||
QFont font;
|
||||
font.setPointSizeF(font.pointSizeF() * 1.3);
|
||||
QFont font;
|
||||
font.setPointSizeF(font.pointSizeF() * 1.3);
|
||||
|
||||
nameInput_ = new TextField(this);
|
||||
nameInput_->setLabel(tr("Name"));
|
||||
nameInput_ = new TextField(this);
|
||||
nameInput_->setLabel(tr("Name"));
|
||||
|
||||
topicInput_ = new TextField(this);
|
||||
topicInput_->setLabel(tr("Topic"));
|
||||
topicInput_ = new TextField(this);
|
||||
topicInput_->setLabel(tr("Topic"));
|
||||
|
||||
aliasInput_ = new TextField(this);
|
||||
aliasInput_->setLabel(tr("Alias"));
|
||||
aliasInput_ = new TextField(this);
|
||||
aliasInput_->setLabel(tr("Alias"));
|
||||
|
||||
auto visibilityLayout = new QHBoxLayout;
|
||||
visibilityLayout->setContentsMargins(0, 10, 0, 10);
|
||||
auto visibilityLayout = new QHBoxLayout;
|
||||
visibilityLayout->setContentsMargins(0, 10, 0, 10);
|
||||
|
||||
auto presetLayout = new QHBoxLayout;
|
||||
presetLayout->setContentsMargins(0, 10, 0, 10);
|
||||
auto presetLayout = new QHBoxLayout;
|
||||
presetLayout->setContentsMargins(0, 10, 0, 10);
|
||||
|
||||
auto visibilityLabel = new QLabel(tr("Room Visibility"), this);
|
||||
visibilityCombo_ = new QComboBox(this);
|
||||
visibilityCombo_->addItem("Private");
|
||||
visibilityCombo_->addItem("Public");
|
||||
auto visibilityLabel = new QLabel(tr("Room Visibility"), this);
|
||||
visibilityCombo_ = new QComboBox(this);
|
||||
visibilityCombo_->addItem("Private");
|
||||
visibilityCombo_->addItem("Public");
|
||||
|
||||
visibilityLayout->addWidget(visibilityLabel);
|
||||
visibilityLayout->addWidget(visibilityCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||
visibilityLayout->addWidget(visibilityLabel);
|
||||
visibilityLayout->addWidget(visibilityCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||
|
||||
auto presetLabel = new QLabel(tr("Room Preset"), this);
|
||||
presetCombo_ = new QComboBox(this);
|
||||
presetCombo_->addItem("Private Chat");
|
||||
presetCombo_->addItem("Public Chat");
|
||||
presetCombo_->addItem("Trusted Private Chat");
|
||||
auto presetLabel = new QLabel(tr("Room Preset"), this);
|
||||
presetCombo_ = new QComboBox(this);
|
||||
presetCombo_->addItem("Private Chat");
|
||||
presetCombo_->addItem("Public Chat");
|
||||
presetCombo_->addItem("Trusted Private Chat");
|
||||
|
||||
presetLayout->addWidget(presetLabel);
|
||||
presetLayout->addWidget(presetCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||
presetLayout->addWidget(presetLabel);
|
||||
presetLayout->addWidget(presetCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||
|
||||
auto directLabel_ = new QLabel(tr("Direct Chat"), this);
|
||||
directToggle_ = new Toggle(this);
|
||||
directToggle_->setActiveColor(QColor("#38A3D8"));
|
||||
directToggle_->setInactiveColor(QColor("gray"));
|
||||
directToggle_->setState(false);
|
||||
auto directLabel_ = new QLabel(tr("Direct Chat"), this);
|
||||
directToggle_ = new Toggle(this);
|
||||
directToggle_->setActiveColor(QColor("#38A3D8"));
|
||||
directToggle_->setInactiveColor(QColor("gray"));
|
||||
directToggle_->setState(false);
|
||||
|
||||
auto directLayout = new QHBoxLayout;
|
||||
directLayout->setContentsMargins(0, 10, 0, 10);
|
||||
directLayout->addWidget(directLabel_);
|
||||
directLayout->addWidget(directToggle_, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||
auto directLayout = new QHBoxLayout;
|
||||
directLayout->setContentsMargins(0, 10, 0, 10);
|
||||
directLayout->addWidget(directLabel_);
|
||||
directLayout->addWidget(directToggle_, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||
|
||||
layout->addWidget(nameInput_);
|
||||
layout->addWidget(topicInput_);
|
||||
layout->addWidget(aliasInput_);
|
||||
layout->addLayout(visibilityLayout);
|
||||
layout->addLayout(presetLayout);
|
||||
layout->addLayout(directLayout);
|
||||
layout->addLayout(buttonLayout);
|
||||
layout->addWidget(nameInput_);
|
||||
layout->addWidget(topicInput_);
|
||||
layout->addWidget(aliasInput_);
|
||||
layout->addLayout(visibilityLayout);
|
||||
layout->addLayout(presetLayout);
|
||||
layout->addLayout(directLayout);
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
|
||||
request_.name = nameInput_->text().toStdString();
|
||||
request_.topic = topicInput_->text().toStdString();
|
||||
request_.room_alias_name = aliasInput_->text().toStdString();
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
|
||||
request_.name = nameInput_->text().toStdString();
|
||||
request_.topic = topicInput_->text().toStdString();
|
||||
request_.room_alias_name = aliasInput_->text().toStdString();
|
||||
|
||||
emit createRoom(request_);
|
||||
emit createRoom(request_);
|
||||
|
||||
clearFields();
|
||||
emit close();
|
||||
});
|
||||
clearFields();
|
||||
emit close();
|
||||
});
|
||||
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
|
||||
clearFields();
|
||||
emit close();
|
||||
});
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
|
||||
clearFields();
|
||||
emit close();
|
||||
});
|
||||
|
||||
connect(visibilityCombo_,
|
||||
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
|
||||
[this](const QString &text) {
|
||||
if (text == "Private") {
|
||||
request_.visibility = mtx::common::RoomVisibility::Private;
|
||||
} else {
|
||||
request_.visibility = mtx::common::RoomVisibility::Public;
|
||||
}
|
||||
});
|
||||
connect(visibilityCombo_,
|
||||
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
|
||||
[this](const QString &text) {
|
||||
if (text == "Private") {
|
||||
request_.visibility = mtx::common::RoomVisibility::Private;
|
||||
} else {
|
||||
request_.visibility = mtx::common::RoomVisibility::Public;
|
||||
}
|
||||
});
|
||||
|
||||
connect(presetCombo_,
|
||||
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
|
||||
[this](const QString &text) {
|
||||
if (text == "Private Chat") {
|
||||
request_.preset = mtx::requests::Preset::PrivateChat;
|
||||
} else if (text == "Public Chat") {
|
||||
request_.preset = mtx::requests::Preset::PublicChat;
|
||||
} else {
|
||||
request_.preset = mtx::requests::Preset::TrustedPrivateChat;
|
||||
}
|
||||
});
|
||||
connect(presetCombo_,
|
||||
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
|
||||
[this](const QString &text) {
|
||||
if (text == "Private Chat") {
|
||||
request_.preset = mtx::requests::Preset::PrivateChat;
|
||||
} else if (text == "Public Chat") {
|
||||
request_.preset = mtx::requests::Preset::PublicChat;
|
||||
} else {
|
||||
request_.preset = mtx::requests::Preset::TrustedPrivateChat;
|
||||
}
|
||||
});
|
||||
|
||||
connect(directToggle_, &Toggle::toggled, this, [this](bool isEnabled) {
|
||||
request_.is_direct = isEnabled;
|
||||
});
|
||||
connect(directToggle_, &Toggle::toggled, this, [this](bool isEnabled) {
|
||||
request_.is_direct = isEnabled;
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
CreateRoom::clearFields()
|
||||
{
|
||||
nameInput_->clear();
|
||||
topicInput_->clear();
|
||||
aliasInput_->clear();
|
||||
nameInput_->clear();
|
||||
topicInput_->clear();
|
||||
aliasInput_->clear();
|
||||
}
|
||||
|
||||
void
|
||||
CreateRoom::showEvent(QShowEvent *event)
|
||||
{
|
||||
nameInput_->setFocus();
|
||||
nameInput_->setFocus();
|
||||
|
||||
QFrame::showEvent(event);
|
||||
QFrame::showEvent(event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,32 +17,32 @@ namespace dialogs {
|
|||
|
||||
class CreateRoom : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
CreateRoom(QWidget *parent = nullptr);
|
||||
CreateRoom(QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void createRoom(const mtx::requests::CreateRoom &request);
|
||||
void createRoom(const mtx::requests::CreateRoom &request);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private:
|
||||
void clearFields();
|
||||
void clearFields();
|
||||
|
||||
QComboBox *visibilityCombo_;
|
||||
QComboBox *presetCombo_;
|
||||
QComboBox *visibilityCombo_;
|
||||
QComboBox *presetCombo_;
|
||||
|
||||
Toggle *directToggle_;
|
||||
Toggle *directToggle_;
|
||||
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
|
||||
TextField *nameInput_;
|
||||
TextField *topicInput_;
|
||||
TextField *aliasInput_;
|
||||
TextField *nameInput_;
|
||||
TextField *topicInput_;
|
||||
TextField *aliasInput_;
|
||||
|
||||
mtx::requests::CreateRoom request_;
|
||||
mtx::requests::CreateRoom request_;
|
||||
};
|
||||
|
||||
} // dialogs
|
||||
|
|
|
|||
|
|
@ -18,56 +18,56 @@ using namespace dialogs;
|
|||
FallbackAuth::FallbackAuth(const QString &authType, const QString &session, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(8);
|
||||
buttonLayout->setMargin(0);
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(8);
|
||||
buttonLayout->setMargin(0);
|
||||
|
||||
openBtn_ = new QPushButton(tr("Open Fallback in Browser"), this);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
confirmBtn_ = new QPushButton(tr("Confirm"), this);
|
||||
confirmBtn_->setDefault(true);
|
||||
openBtn_ = new QPushButton(tr("Open Fallback in Browser"), this);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
confirmBtn_ = new QPushButton(tr("Confirm"), this);
|
||||
confirmBtn_->setDefault(true);
|
||||
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(openBtn_);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(openBtn_);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
|
||||
QFont font;
|
||||
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
|
||||
QFont font;
|
||||
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
|
||||
|
||||
auto label = new QLabel(
|
||||
tr("Open the fallback, follow the steps and confirm after completing them."), this);
|
||||
label->setFont(font);
|
||||
auto label = new QLabel(
|
||||
tr("Open the fallback, follow the steps and confirm after completing them."), this);
|
||||
label->setFont(font);
|
||||
|
||||
layout->addWidget(label);
|
||||
layout->addLayout(buttonLayout);
|
||||
layout->addWidget(label);
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
connect(openBtn_, &QPushButton::clicked, [session, authType]() {
|
||||
const auto url = QString("https://%1:%2/_matrix/client/r0/auth/%4/"
|
||||
"fallback/web?session=%3")
|
||||
.arg(QString::fromStdString(http::client()->server()))
|
||||
.arg(http::client()->port())
|
||||
.arg(session)
|
||||
.arg(authType);
|
||||
connect(openBtn_, &QPushButton::clicked, [session, authType]() {
|
||||
const auto url = QString("https://%1:%2/_matrix/client/r0/auth/%4/"
|
||||
"fallback/web?session=%3")
|
||||
.arg(QString::fromStdString(http::client()->server()))
|
||||
.arg(http::client()->port())
|
||||
.arg(session)
|
||||
.arg(authType);
|
||||
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit confirmation();
|
||||
emit close();
|
||||
});
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit cancel();
|
||||
emit close();
|
||||
});
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit confirmation();
|
||||
emit close();
|
||||
});
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit cancel();
|
||||
emit close();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,18 +13,18 @@ namespace dialogs {
|
|||
|
||||
class FallbackAuth : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FallbackAuth(const QString &authType, const QString &session, QWidget *parent = nullptr);
|
||||
FallbackAuth(const QString &authType, const QString &session, QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void confirmation();
|
||||
void cancel();
|
||||
void confirmation();
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
QPushButton *openBtn_;
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
QPushButton *openBtn_;
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
};
|
||||
} // dialogs
|
||||
|
|
|
|||
|
|
@ -19,84 +19,83 @@ ImageOverlay::ImageOverlay(QPixmap image, QWidget *parent)
|
|||
: QWidget{parent}
|
||||
, originalImage_{image}
|
||||
{
|
||||
setMouseTracking(true);
|
||||
setParent(nullptr);
|
||||
setMouseTracking(true);
|
||||
setParent(nullptr);
|
||||
|
||||
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
|
||||
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
|
||||
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setWindowState(Qt::WindowFullScreen);
|
||||
close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this);
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setWindowState(Qt::WindowFullScreen);
|
||||
close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this);
|
||||
|
||||
connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing);
|
||||
connect(this, &ImageOverlay::closing, this, &ImageOverlay::close);
|
||||
connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing);
|
||||
connect(this, &ImageOverlay::closing, this, &ImageOverlay::close);
|
||||
|
||||
raise();
|
||||
raise();
|
||||
}
|
||||
|
||||
void
|
||||
ImageOverlay::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
Q_UNUSED(event);
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Full screen overlay.
|
||||
painter.fillRect(QRect(0, 0, width(), height()), QColor(55, 55, 55, 170));
|
||||
// Full screen overlay.
|
||||
painter.fillRect(QRect(0, 0, width(), height()), QColor(55, 55, 55, 170));
|
||||
|
||||
// Left and Right margins
|
||||
int outer_margin = width() * 0.12;
|
||||
int buttonSize = 36;
|
||||
int margin = outer_margin * 0.1;
|
||||
// Left and Right margins
|
||||
int outer_margin = width() * 0.12;
|
||||
int buttonSize = 36;
|
||||
int margin = outer_margin * 0.1;
|
||||
|
||||
int max_width = width() - 2 * outer_margin;
|
||||
int max_height = height();
|
||||
int max_width = width() - 2 * outer_margin;
|
||||
int max_height = height();
|
||||
|
||||
image_ = utils::scaleDown(max_width, max_height, originalImage_);
|
||||
image_ = utils::scaleDown(max_width, max_height, originalImage_);
|
||||
|
||||
int diff_x = max_width - image_.width();
|
||||
int diff_y = max_height - image_.height();
|
||||
int diff_x = max_width - image_.width();
|
||||
int diff_y = max_height - image_.height();
|
||||
|
||||
content_ = QRect(outer_margin + diff_x / 2, diff_y / 2, image_.width(), image_.height());
|
||||
close_button_ = QRect(width() - margin - buttonSize, margin, buttonSize, buttonSize);
|
||||
save_button_ =
|
||||
QRect(width() - (2 * margin) - (2 * buttonSize), margin, buttonSize, buttonSize);
|
||||
content_ = QRect(outer_margin + diff_x / 2, diff_y / 2, image_.width(), image_.height());
|
||||
close_button_ = QRect(width() - margin - buttonSize, margin, buttonSize, buttonSize);
|
||||
save_button_ = QRect(width() - (2 * margin) - (2 * buttonSize), margin, buttonSize, buttonSize);
|
||||
|
||||
// Draw main content_.
|
||||
painter.drawPixmap(content_, image_);
|
||||
// Draw main content_.
|
||||
painter.drawPixmap(content_, image_);
|
||||
|
||||
// Draw top right corner X.
|
||||
QPen pen;
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setWidthF(5);
|
||||
pen.setColor("gray");
|
||||
// Draw top right corner X.
|
||||
QPen pen;
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setWidthF(5);
|
||||
pen.setColor("gray");
|
||||
|
||||
auto center = close_button_.center();
|
||||
auto center = close_button_.center();
|
||||
|
||||
painter.setPen(pen);
|
||||
painter.drawLine(center - QPointF(15, 15), center + QPointF(15, 15));
|
||||
painter.drawLine(center + QPointF(15, -15), center - QPointF(15, -15));
|
||||
painter.setPen(pen);
|
||||
painter.drawLine(center - QPointF(15, 15), center + QPointF(15, 15));
|
||||
painter.drawLine(center + QPointF(15, -15), center - QPointF(15, -15));
|
||||
|
||||
// Draw download button
|
||||
center = save_button_.center();
|
||||
painter.drawLine(center - QPointF(0, 15), center + QPointF(0, 15));
|
||||
painter.drawLine(center - QPointF(15, 0), center + QPointF(0, 15));
|
||||
painter.drawLine(center + QPointF(0, 15), center + QPointF(15, 0));
|
||||
// Draw download button
|
||||
center = save_button_.center();
|
||||
painter.drawLine(center - QPointF(0, 15), center + QPointF(0, 15));
|
||||
painter.drawLine(center - QPointF(15, 0), center + QPointF(0, 15));
|
||||
painter.drawLine(center + QPointF(0, 15), center + QPointF(15, 0));
|
||||
}
|
||||
|
||||
void
|
||||
ImageOverlay::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() != Qt::LeftButton)
|
||||
return;
|
||||
if (event->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
if (close_button_.contains(event->pos()))
|
||||
emit closing();
|
||||
else if (save_button_.contains(event->pos()))
|
||||
emit saving();
|
||||
else if (!content_.contains(event->pos()))
|
||||
emit closing();
|
||||
if (close_button_.contains(event->pos()))
|
||||
emit closing();
|
||||
else if (save_button_.contains(event->pos()))
|
||||
emit saving();
|
||||
else if (!content_.contains(event->pos()))
|
||||
emit closing();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,25 +14,25 @@ namespace dialogs {
|
|||
|
||||
class ImageOverlay : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
ImageOverlay(QPixmap image, QWidget *parent = nullptr);
|
||||
ImageOverlay(QPixmap image, QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
signals:
|
||||
void closing();
|
||||
void saving();
|
||||
void closing();
|
||||
void saving();
|
||||
|
||||
private:
|
||||
QPixmap originalImage_;
|
||||
QPixmap image_;
|
||||
QPixmap originalImage_;
|
||||
QPixmap image_;
|
||||
|
||||
QRect content_;
|
||||
QRect close_button_;
|
||||
QRect save_button_;
|
||||
QShortcut *close_shortcut_;
|
||||
QRect content_;
|
||||
QRect close_button_;
|
||||
QRect save_button_;
|
||||
QShortcut *close_shortcut_;
|
||||
};
|
||||
} // dialogs
|
||||
|
|
|
|||
|
|
@ -16,58 +16,58 @@ using namespace dialogs;
|
|||
JoinRoom::JoinRoom(QWidget *parent)
|
||||
: QFrame(parent)
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(15);
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(15);
|
||||
|
||||
confirmBtn_ = new QPushButton(tr("Join"), this);
|
||||
confirmBtn_->setDefault(true);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
confirmBtn_ = new QPushButton(tr("Join"), this);
|
||||
confirmBtn_->setDefault(true);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
|
||||
roomInput_ = new TextField(this);
|
||||
roomInput_->setLabel(tr("Room ID or alias"));
|
||||
roomInput_ = new TextField(this);
|
||||
roomInput_->setLabel(tr("Room ID or alias"));
|
||||
|
||||
layout->addWidget(roomInput_);
|
||||
layout->addLayout(buttonLayout);
|
||||
layout->addStretch(1);
|
||||
layout->addWidget(roomInput_);
|
||||
layout->addLayout(buttonLayout);
|
||||
layout->addStretch(1);
|
||||
|
||||
connect(roomInput_, &QLineEdit::returnPressed, this, &JoinRoom::handleInput);
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, &JoinRoom::handleInput);
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, &JoinRoom::close);
|
||||
connect(roomInput_, &QLineEdit::returnPressed, this, &JoinRoom::handleInput);
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, &JoinRoom::handleInput);
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, &JoinRoom::close);
|
||||
}
|
||||
|
||||
void
|
||||
JoinRoom::handleInput()
|
||||
{
|
||||
if (roomInput_->text().isEmpty())
|
||||
return;
|
||||
if (roomInput_->text().isEmpty())
|
||||
return;
|
||||
|
||||
// TODO: input validation with error messages.
|
||||
emit joinRoom(roomInput_->text());
|
||||
roomInput_->clear();
|
||||
// TODO: input validation with error messages.
|
||||
emit joinRoom(roomInput_->text());
|
||||
roomInput_->clear();
|
||||
|
||||
emit close();
|
||||
emit close();
|
||||
}
|
||||
|
||||
void
|
||||
JoinRoom::showEvent(QShowEvent *event)
|
||||
{
|
||||
roomInput_->setFocus();
|
||||
roomInput_->setFocus();
|
||||
|
||||
QFrame::showEvent(event);
|
||||
QFrame::showEvent(event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,24 +13,24 @@ namespace dialogs {
|
|||
|
||||
class JoinRoom : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
JoinRoom(QWidget *parent = nullptr);
|
||||
JoinRoom(QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void joinRoom(const QString &room);
|
||||
void joinRoom(const QString &room);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void handleInput();
|
||||
void handleInput();
|
||||
|
||||
private:
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
|
||||
TextField *roomInput_;
|
||||
TextField *roomInput_;
|
||||
};
|
||||
|
||||
} // dialogs
|
||||
|
|
|
|||
|
|
@ -15,39 +15,39 @@ using namespace dialogs;
|
|||
LeaveRoom::LeaveRoom(QWidget *parent)
|
||||
: QFrame(parent)
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(0);
|
||||
buttonLayout->setMargin(0);
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(0);
|
||||
buttonLayout->setMargin(0);
|
||||
|
||||
confirmBtn_ = new QPushButton("Leave", this);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
cancelBtn_->setDefault(true);
|
||||
confirmBtn_ = new QPushButton("Leave", this);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
cancelBtn_->setDefault(true);
|
||||
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->setSpacing(15);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->setSpacing(15);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
|
||||
auto label = new QLabel(tr("Are you sure you want to leave?"), this);
|
||||
auto label = new QLabel(tr("Are you sure you want to leave?"), this);
|
||||
|
||||
layout->addWidget(label);
|
||||
layout->addLayout(buttonLayout);
|
||||
layout->addWidget(label);
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit leaving();
|
||||
emit close();
|
||||
});
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, &LeaveRoom::close);
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit leaving();
|
||||
emit close();
|
||||
});
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, &LeaveRoom::close);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,15 +12,15 @@ namespace dialogs {
|
|||
|
||||
class LeaveRoom : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LeaveRoom(QWidget *parent = nullptr);
|
||||
explicit LeaveRoom(QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void leaving();
|
||||
void leaving();
|
||||
|
||||
private:
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
};
|
||||
} // dialogs
|
||||
|
|
|
|||
|
|
@ -15,40 +15,40 @@ using namespace dialogs;
|
|||
Logout::Logout(QWidget *parent)
|
||||
: QFrame(parent)
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(0);
|
||||
buttonLayout->setMargin(0);
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(0);
|
||||
buttonLayout->setMargin(0);
|
||||
|
||||
confirmBtn_ = new QPushButton("Logout", this);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
cancelBtn_->setDefault(true);
|
||||
confirmBtn_ = new QPushButton("Logout", this);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
cancelBtn_->setDefault(true);
|
||||
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->setSpacing(15);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->setSpacing(15);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
|
||||
auto label = new QLabel(tr("Logout. Are you sure?"), this);
|
||||
auto label = new QLabel(tr("Logout. Are you sure?"), this);
|
||||
|
||||
layout->addWidget(label);
|
||||
layout->addLayout(buttonLayout);
|
||||
layout->addStretch(1);
|
||||
layout->addWidget(label);
|
||||
layout->addLayout(buttonLayout);
|
||||
layout->addStretch(1);
|
||||
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit loggingOut();
|
||||
emit close();
|
||||
});
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, &Logout::close);
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit loggingOut();
|
||||
emit close();
|
||||
});
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, &Logout::close);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,15 +13,15 @@ namespace dialogs {
|
|||
|
||||
class Logout : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Logout(QWidget *parent = nullptr);
|
||||
explicit Logout(QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void loggingOut();
|
||||
void loggingOut();
|
||||
|
||||
private:
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
};
|
||||
} // dialogs
|
||||
|
|
|
|||
|
|
@ -29,188 +29,185 @@ PreviewUploadOverlay::PreviewUploadOverlay(QWidget *parent)
|
|||
, upload_{tr("Upload"), this}
|
||||
, cancel_{tr("Cancel"), this}
|
||||
{
|
||||
auto hlayout = new QHBoxLayout;
|
||||
hlayout->addStretch(1);
|
||||
hlayout->addWidget(&cancel_);
|
||||
hlayout->addWidget(&upload_);
|
||||
hlayout->setMargin(0);
|
||||
auto hlayout = new QHBoxLayout;
|
||||
hlayout->addStretch(1);
|
||||
hlayout->addWidget(&cancel_);
|
||||
hlayout->addWidget(&upload_);
|
||||
hlayout->setMargin(0);
|
||||
|
||||
auto vlayout = new QVBoxLayout{this};
|
||||
vlayout->addWidget(&titleLabel_);
|
||||
vlayout->addWidget(&infoLabel_);
|
||||
vlayout->addWidget(&fileName_);
|
||||
vlayout->addLayout(hlayout);
|
||||
vlayout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
vlayout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
auto vlayout = new QVBoxLayout{this};
|
||||
vlayout->addWidget(&titleLabel_);
|
||||
vlayout->addWidget(&infoLabel_);
|
||||
vlayout->addWidget(&fileName_);
|
||||
vlayout->addLayout(hlayout);
|
||||
vlayout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
vlayout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
|
||||
upload_.setDefault(true);
|
||||
connect(&upload_, &QPushButton::clicked, [this]() {
|
||||
emit confirmUpload(data_, mediaType_, fileName_.text());
|
||||
close();
|
||||
});
|
||||
upload_.setDefault(true);
|
||||
connect(&upload_, &QPushButton::clicked, [this]() {
|
||||
emit confirmUpload(data_, mediaType_, fileName_.text());
|
||||
close();
|
||||
});
|
||||
|
||||
connect(&fileName_, &QLineEdit::returnPressed, this, [this]() {
|
||||
emit confirmUpload(data_, mediaType_, fileName_.text());
|
||||
close();
|
||||
});
|
||||
connect(&fileName_, &QLineEdit::returnPressed, this, [this]() {
|
||||
emit confirmUpload(data_, mediaType_, fileName_.text());
|
||||
close();
|
||||
});
|
||||
|
||||
connect(&cancel_, &QPushButton::clicked, this, [this]() {
|
||||
emit aborted();
|
||||
close();
|
||||
});
|
||||
connect(&cancel_, &QPushButton::clicked, this, [this]() {
|
||||
emit aborted();
|
||||
close();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
PreviewUploadOverlay::init()
|
||||
{
|
||||
QSize winsize;
|
||||
QPoint center;
|
||||
QSize winsize;
|
||||
QPoint center;
|
||||
|
||||
auto window = MainWindow::instance();
|
||||
if (window) {
|
||||
winsize = window->frameGeometry().size();
|
||||
center = window->frameGeometry().center();
|
||||
} else {
|
||||
nhlog::ui()->warn("unable to retrieve MainWindow's size");
|
||||
}
|
||||
auto window = MainWindow::instance();
|
||||
if (window) {
|
||||
winsize = window->frameGeometry().size();
|
||||
center = window->frameGeometry().center();
|
||||
} else {
|
||||
nhlog::ui()->warn("unable to retrieve MainWindow's size");
|
||||
}
|
||||
|
||||
fileName_.setText(QFileInfo{filePath_}.fileName());
|
||||
fileName_.setText(QFileInfo{filePath_}.fileName());
|
||||
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
|
||||
QFont font;
|
||||
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
|
||||
QFont font;
|
||||
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
|
||||
|
||||
titleLabel_.setFont(font);
|
||||
titleLabel_.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
titleLabel_.setAlignment(Qt::AlignCenter);
|
||||
infoLabel_.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
fileName_.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
fileName_.setAlignment(Qt::AlignCenter);
|
||||
upload_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
cancel_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
titleLabel_.setFont(font);
|
||||
titleLabel_.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
titleLabel_.setAlignment(Qt::AlignCenter);
|
||||
infoLabel_.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
fileName_.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
fileName_.setAlignment(Qt::AlignCenter);
|
||||
upload_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
cancel_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
|
||||
if (isImage_) {
|
||||
infoLabel_.setAlignment(Qt::AlignCenter);
|
||||
if (isImage_) {
|
||||
infoLabel_.setAlignment(Qt::AlignCenter);
|
||||
|
||||
const auto maxWidth = winsize.width() * 0.8;
|
||||
const auto maxHeight = winsize.height() * 0.8;
|
||||
const auto maxWidth = winsize.width() * 0.8;
|
||||
const auto maxHeight = winsize.height() * 0.8;
|
||||
|
||||
// Scale image preview to fit into the application window.
|
||||
infoLabel_.setPixmap(utils::scaleDown(maxWidth, maxHeight, image_));
|
||||
move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
|
||||
} else {
|
||||
infoLabel_.setAlignment(Qt::AlignLeft);
|
||||
}
|
||||
infoLabel_.setScaledContents(false);
|
||||
// Scale image preview to fit into the application window.
|
||||
infoLabel_.setPixmap(utils::scaleDown(maxWidth, maxHeight, image_));
|
||||
move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
|
||||
} else {
|
||||
infoLabel_.setAlignment(Qt::AlignLeft);
|
||||
}
|
||||
infoLabel_.setScaledContents(false);
|
||||
|
||||
show();
|
||||
show();
|
||||
}
|
||||
|
||||
void
|
||||
PreviewUploadOverlay::setLabels(const QString &type, const QString &mime, uint64_t upload_size)
|
||||
{
|
||||
if (mediaType_.split('/')[0] == "image") {
|
||||
if (!image_.loadFromData(data_)) {
|
||||
titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
|
||||
} else {
|
||||
titleLabel_.setText(QString{tr(DEFAULT)}.arg(mediaType_));
|
||||
}
|
||||
isImage_ = true;
|
||||
if (mediaType_.split('/')[0] == "image") {
|
||||
if (!image_.loadFromData(data_)) {
|
||||
titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
|
||||
} else {
|
||||
auto const info = QString{tr("Media type: %1\n"
|
||||
"Media size: %2\n")}
|
||||
.arg(mime)
|
||||
.arg(utils::humanReadableFileSize(upload_size));
|
||||
|
||||
titleLabel_.setText(QString{tr(DEFAULT)}.arg("file"));
|
||||
infoLabel_.setText(info);
|
||||
titleLabel_.setText(QString{tr(DEFAULT)}.arg(mediaType_));
|
||||
}
|
||||
isImage_ = true;
|
||||
} else {
|
||||
auto const info = QString{tr("Media type: %1\n"
|
||||
"Media size: %2\n")}
|
||||
.arg(mime)
|
||||
.arg(utils::humanReadableFileSize(upload_size));
|
||||
|
||||
titleLabel_.setText(QString{tr(DEFAULT)}.arg("file"));
|
||||
infoLabel_.setText(info);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PreviewUploadOverlay::setPreview(const QImage &src, const QString &mime)
|
||||
{
|
||||
nhlog::ui()->info("Pasting image with size: {}x{}, format: {}",
|
||||
src.height(),
|
||||
src.width(),
|
||||
mime.toStdString());
|
||||
nhlog::ui()->info(
|
||||
"Pasting image with size: {}x{}, format: {}", src.height(), src.width(), mime.toStdString());
|
||||
|
||||
auto const &split = mime.split('/');
|
||||
auto const &type = split[1];
|
||||
|
||||
QBuffer buffer(&data_);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
if (src.save(&buffer, type.toStdString().c_str()))
|
||||
titleLabel_.setText(QString{tr(DEFAULT)}.arg("image"));
|
||||
else
|
||||
titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
|
||||
|
||||
mediaType_ = mime;
|
||||
filePath_ = "clipboard." + type;
|
||||
image_.convertFromImage(src);
|
||||
isImage_ = true;
|
||||
auto const &split = mime.split('/');
|
||||
auto const &type = split[1];
|
||||
|
||||
QBuffer buffer(&data_);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
if (src.save(&buffer, type.toStdString().c_str()))
|
||||
titleLabel_.setText(QString{tr(DEFAULT)}.arg("image"));
|
||||
init();
|
||||
else
|
||||
titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
|
||||
|
||||
mediaType_ = mime;
|
||||
filePath_ = "clipboard." + type;
|
||||
image_.convertFromImage(src);
|
||||
isImage_ = true;
|
||||
|
||||
titleLabel_.setText(QString{tr(DEFAULT)}.arg("image"));
|
||||
init();
|
||||
}
|
||||
|
||||
void
|
||||
PreviewUploadOverlay::setPreview(const QByteArray data, const QString &mime)
|
||||
{
|
||||
auto const &split = mime.split('/');
|
||||
auto const &type = split[1];
|
||||
auto const &split = mime.split('/');
|
||||
auto const &type = split[1];
|
||||
|
||||
data_ = data;
|
||||
mediaType_ = mime;
|
||||
filePath_ = "clipboard." + type;
|
||||
isImage_ = false;
|
||||
data_ = data;
|
||||
mediaType_ = mime;
|
||||
filePath_ = "clipboard." + type;
|
||||
isImage_ = false;
|
||||
|
||||
setLabels(type, mime, data_.size());
|
||||
init();
|
||||
setLabels(type, mime, data_.size());
|
||||
init();
|
||||
}
|
||||
|
||||
void
|
||||
PreviewUploadOverlay::setPreview(const QString &path)
|
||||
{
|
||||
QFile file{path};
|
||||
QFile file{path};
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
nhlog::ui()->warn("Failed to open file ({}): {}",
|
||||
path.toStdString(),
|
||||
file.errorString().toStdString());
|
||||
close();
|
||||
return;
|
||||
}
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
nhlog::ui()->warn(
|
||||
"Failed to open file ({}): {}", path.toStdString(), file.errorString().toStdString());
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
QMimeDatabase db;
|
||||
auto mime = db.mimeTypeForFileNameAndData(path, &file);
|
||||
QMimeDatabase db;
|
||||
auto mime = db.mimeTypeForFileNameAndData(path, &file);
|
||||
|
||||
if ((data_ = file.readAll()).isEmpty()) {
|
||||
nhlog::ui()->warn("Failed to read media: {}", file.errorString().toStdString());
|
||||
close();
|
||||
return;
|
||||
}
|
||||
if ((data_ = file.readAll()).isEmpty()) {
|
||||
nhlog::ui()->warn("Failed to read media: {}", file.errorString().toStdString());
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
auto const &split = mime.name().split('/');
|
||||
auto const &split = mime.name().split('/');
|
||||
|
||||
mediaType_ = mime.name();
|
||||
filePath_ = file.fileName();
|
||||
isImage_ = false;
|
||||
mediaType_ = mime.name();
|
||||
filePath_ = file.fileName();
|
||||
isImage_ = false;
|
||||
|
||||
setLabels(split[1], mime.name(), data_.size());
|
||||
init();
|
||||
setLabels(split[1], mime.name(), data_.size());
|
||||
init();
|
||||
}
|
||||
|
||||
void
|
||||
PreviewUploadOverlay::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (event->matches(QKeySequence::Cancel)) {
|
||||
emit aborted();
|
||||
close();
|
||||
} else {
|
||||
QWidget::keyPressEvent(event);
|
||||
}
|
||||
if (event->matches(QKeySequence::Cancel)) {
|
||||
emit aborted();
|
||||
close();
|
||||
} else {
|
||||
QWidget::keyPressEvent(event);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,35 +18,35 @@ namespace dialogs {
|
|||
|
||||
class PreviewUploadOverlay : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
PreviewUploadOverlay(QWidget *parent = nullptr);
|
||||
PreviewUploadOverlay(QWidget *parent = nullptr);
|
||||
|
||||
void setPreview(const QImage &src, const QString &mime);
|
||||
void setPreview(const QByteArray data, const QString &mime);
|
||||
void setPreview(const QString &path);
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
void setPreview(const QImage &src, const QString &mime);
|
||||
void setPreview(const QByteArray data, const QString &mime);
|
||||
void setPreview(const QString &path);
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
|
||||
signals:
|
||||
void confirmUpload(const QByteArray data, const QString &media, const QString &filename);
|
||||
void aborted();
|
||||
void confirmUpload(const QByteArray data, const QString &media, const QString &filename);
|
||||
void aborted();
|
||||
|
||||
private:
|
||||
void init();
|
||||
void setLabels(const QString &type, const QString &mime, uint64_t upload_size);
|
||||
void init();
|
||||
void setLabels(const QString &type, const QString &mime, uint64_t upload_size);
|
||||
|
||||
bool isImage_;
|
||||
QPixmap image_;
|
||||
bool isImage_;
|
||||
QPixmap image_;
|
||||
|
||||
QByteArray data_;
|
||||
QString filePath_;
|
||||
QString mediaType_;
|
||||
QByteArray data_;
|
||||
QString filePath_;
|
||||
QString mediaType_;
|
||||
|
||||
QLabel titleLabel_;
|
||||
QLabel infoLabel_;
|
||||
QLineEdit fileName_;
|
||||
QLabel titleLabel_;
|
||||
QLabel infoLabel_;
|
||||
QLineEdit fileName_;
|
||||
|
||||
QPushButton upload_;
|
||||
QPushButton cancel_;
|
||||
QPushButton upload_;
|
||||
QPushButton cancel_;
|
||||
};
|
||||
} // dialogs
|
||||
|
|
|
|||
|
|
@ -18,54 +18,54 @@ using namespace dialogs;
|
|||
ReCaptcha::ReCaptcha(const QString &session, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(8);
|
||||
buttonLayout->setMargin(0);
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(8);
|
||||
buttonLayout->setMargin(0);
|
||||
|
||||
openCaptchaBtn_ = new QPushButton("Open reCAPTCHA", this);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
confirmBtn_ = new QPushButton(tr("Confirm"), this);
|
||||
confirmBtn_->setDefault(true);
|
||||
openCaptchaBtn_ = new QPushButton("Open reCAPTCHA", this);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
confirmBtn_ = new QPushButton(tr("Confirm"), this);
|
||||
confirmBtn_->setDefault(true);
|
||||
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(openCaptchaBtn_);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(openCaptchaBtn_);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
|
||||
QFont font;
|
||||
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
|
||||
QFont font;
|
||||
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
|
||||
|
||||
auto label = new QLabel(tr("Solve the reCAPTCHA and press the confirm button"), this);
|
||||
label->setFont(font);
|
||||
auto label = new QLabel(tr("Solve the reCAPTCHA and press the confirm button"), this);
|
||||
label->setFont(font);
|
||||
|
||||
layout->addWidget(label);
|
||||
layout->addLayout(buttonLayout);
|
||||
layout->addWidget(label);
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
connect(openCaptchaBtn_, &QPushButton::clicked, [session]() {
|
||||
const auto url = QString("https://%1:%2/_matrix/client/r0/auth/m.login.recaptcha/"
|
||||
"fallback/web?session=%3")
|
||||
.arg(QString::fromStdString(http::client()->server()))
|
||||
.arg(http::client()->port())
|
||||
.arg(session);
|
||||
connect(openCaptchaBtn_, &QPushButton::clicked, [session]() {
|
||||
const auto url = QString("https://%1:%2/_matrix/client/r0/auth/m.login.recaptcha/"
|
||||
"fallback/web?session=%3")
|
||||
.arg(QString::fromStdString(http::client()->server()))
|
||||
.arg(http::client()->port())
|
||||
.arg(session);
|
||||
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit confirmation();
|
||||
emit close();
|
||||
});
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit cancel();
|
||||
emit close();
|
||||
});
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit confirmation();
|
||||
emit close();
|
||||
});
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
|
||||
emit cancel();
|
||||
emit close();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,18 +12,18 @@ namespace dialogs {
|
|||
|
||||
class ReCaptcha : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ReCaptcha(const QString &session, QWidget *parent = nullptr);
|
||||
ReCaptcha(const QString &session, QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void confirmation();
|
||||
void cancel();
|
||||
void confirmation();
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
QPushButton *openCaptchaBtn_;
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
QPushButton *openCaptchaBtn_;
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
};
|
||||
} // dialogs
|
||||
|
|
|
|||
|
|
@ -14,63 +14,60 @@ using namespace emoji;
|
|||
int
|
||||
EmojiModel::categoryToIndex(int category)
|
||||
{
|
||||
auto dist = std::distance(Provider::emoji.begin(),
|
||||
std::lower_bound(Provider::emoji.begin(),
|
||||
Provider::emoji.end(),
|
||||
static_cast<Emoji::Category>(category),
|
||||
[](const struct Emoji &e, Emoji::Category c) {
|
||||
return e.category < c;
|
||||
}));
|
||||
auto dist = std::distance(
|
||||
Provider::emoji.begin(),
|
||||
std::lower_bound(Provider::emoji.begin(),
|
||||
Provider::emoji.end(),
|
||||
static_cast<Emoji::Category>(category),
|
||||
[](const struct Emoji &e, Emoji::Category c) { return e.category < c; }));
|
||||
|
||||
return static_cast<int>(dist);
|
||||
return static_cast<int>(dist);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
EmojiModel::roleNames() const
|
||||
{
|
||||
static QHash<int, QByteArray> roles;
|
||||
static QHash<int, QByteArray> roles;
|
||||
|
||||
if (roles.isEmpty()) {
|
||||
roles = QAbstractListModel::roleNames();
|
||||
roles[static_cast<int>(EmojiModel::Roles::Unicode)] = QByteArrayLiteral("unicode");
|
||||
roles[static_cast<int>(EmojiModel::Roles::ShortName)] =
|
||||
QByteArrayLiteral("shortName");
|
||||
roles[static_cast<int>(EmojiModel::Roles::Category)] =
|
||||
QByteArrayLiteral("category");
|
||||
roles[static_cast<int>(EmojiModel::Roles::Emoji)] = QByteArrayLiteral("emoji");
|
||||
}
|
||||
if (roles.isEmpty()) {
|
||||
roles = QAbstractListModel::roleNames();
|
||||
roles[static_cast<int>(EmojiModel::Roles::Unicode)] = QByteArrayLiteral("unicode");
|
||||
roles[static_cast<int>(EmojiModel::Roles::ShortName)] = QByteArrayLiteral("shortName");
|
||||
roles[static_cast<int>(EmojiModel::Roles::Category)] = QByteArrayLiteral("category");
|
||||
roles[static_cast<int>(EmojiModel::Roles::Emoji)] = QByteArrayLiteral("emoji");
|
||||
}
|
||||
|
||||
return roles;
|
||||
return roles;
|
||||
}
|
||||
|
||||
int
|
||||
EmojiModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent == QModelIndex() ? Provider::emoji.count() : 0;
|
||||
return parent == QModelIndex() ? Provider::emoji.count() : 0;
|
||||
}
|
||||
|
||||
QVariant
|
||||
EmojiModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case CompletionModel::CompletionRole:
|
||||
case static_cast<int>(EmojiModel::Roles::Unicode):
|
||||
return Provider::emoji[index.row()].unicode;
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case CompletionModel::CompletionRole:
|
||||
case static_cast<int>(EmojiModel::Roles::Unicode):
|
||||
return Provider::emoji[index.row()].unicode;
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
case CompletionModel::SearchRole:
|
||||
case static_cast<int>(EmojiModel::Roles::ShortName):
|
||||
return Provider::emoji[index.row()].shortName;
|
||||
case Qt::ToolTipRole:
|
||||
case CompletionModel::SearchRole:
|
||||
case static_cast<int>(EmojiModel::Roles::ShortName):
|
||||
return Provider::emoji[index.row()].shortName;
|
||||
|
||||
case static_cast<int>(EmojiModel::Roles::Category):
|
||||
return QVariant::fromValue(Provider::emoji[index.row()].category);
|
||||
case static_cast<int>(EmojiModel::Roles::Category):
|
||||
return QVariant::fromValue(Provider::emoji[index.row()].category);
|
||||
|
||||
case static_cast<int>(EmojiModel::Roles::Emoji):
|
||||
return QVariant::fromValue(Provider::emoji[index.row()]);
|
||||
}
|
||||
case static_cast<int>(EmojiModel::Roles::Emoji):
|
||||
return QVariant::fromValue(Provider::emoji[index.row()]);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,22 +18,22 @@ namespace emoji {
|
|||
*/
|
||||
class EmojiModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
Unicode = Qt::UserRole, // unicode of emoji
|
||||
Category, // category of emoji
|
||||
ShortName, // shortext of the emoji
|
||||
Emoji, // Contains everything from the Emoji
|
||||
};
|
||||
enum Roles
|
||||
{
|
||||
Unicode = Qt::UserRole, // unicode of emoji
|
||||
Category, // category of emoji
|
||||
ShortName, // shortext of the emoji
|
||||
Emoji, // Contains everything from the Emoji
|
||||
};
|
||||
|
||||
using QAbstractListModel::QAbstractListModel;
|
||||
using QAbstractListModel::QAbstractListModel;
|
||||
|
||||
Q_INVOKABLE int categoryToIndex(int category);
|
||||
Q_INVOKABLE int categoryToIndex(int category);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@
|
|||
class MacHelper
|
||||
{
|
||||
public:
|
||||
static void showEmojiWindow();
|
||||
static void initializeMenus();
|
||||
static void showEmojiWindow();
|
||||
static void initializeMenus();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,37 +16,37 @@ Q_NAMESPACE
|
|||
|
||||
struct Emoji
|
||||
{
|
||||
Q_GADGET
|
||||
Q_GADGET
|
||||
public:
|
||||
enum class Category
|
||||
{
|
||||
People,
|
||||
Nature,
|
||||
Food,
|
||||
Activity,
|
||||
Travel,
|
||||
Objects,
|
||||
Symbols,
|
||||
Flags,
|
||||
Search
|
||||
};
|
||||
Q_ENUM(Category)
|
||||
enum class Category
|
||||
{
|
||||
People,
|
||||
Nature,
|
||||
Food,
|
||||
Activity,
|
||||
Travel,
|
||||
Objects,
|
||||
Symbols,
|
||||
Flags,
|
||||
Search
|
||||
};
|
||||
Q_ENUM(Category)
|
||||
|
||||
Q_PROPERTY(const QString &unicode MEMBER unicode)
|
||||
Q_PROPERTY(const QString &shortName MEMBER shortName)
|
||||
Q_PROPERTY(emoji::Emoji::Category category MEMBER category)
|
||||
Q_PROPERTY(const QString &unicode MEMBER unicode)
|
||||
Q_PROPERTY(const QString &shortName MEMBER shortName)
|
||||
Q_PROPERTY(emoji::Emoji::Category category MEMBER category)
|
||||
|
||||
public:
|
||||
QString unicode;
|
||||
QString shortName;
|
||||
Category category;
|
||||
QString unicode;
|
||||
QString shortName;
|
||||
Category category;
|
||||
};
|
||||
|
||||
class Provider
|
||||
{
|
||||
public:
|
||||
// all emoji for QML purposes
|
||||
static const QVector<Emoji> emoji;
|
||||
// all emoji for QML purposes
|
||||
static const QVector<Emoji> emoji;
|
||||
};
|
||||
|
||||
} // namespace emoji
|
||||
|
|
|
|||
391
src/main.cpp
391
src/main.cpp
|
|
@ -48,47 +48,47 @@ QQmlDebuggingEnabler enabler;
|
|||
void
|
||||
stacktraceHandler(int signum)
|
||||
{
|
||||
std::signal(signum, SIG_DFL);
|
||||
std::signal(signum, SIG_DFL);
|
||||
|
||||
// boost::stacktrace::safe_dump_to("./nheko-backtrace.dump");
|
||||
// boost::stacktrace::safe_dump_to("./nheko-backtrace.dump");
|
||||
|
||||
// see
|
||||
// https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes/77336#77336
|
||||
void *array[50];
|
||||
size_t size;
|
||||
// see
|
||||
// https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes/77336#77336
|
||||
void *array[50];
|
||||
size_t size;
|
||||
|
||||
// get void*'s for all entries on the stack
|
||||
size = backtrace(array, 50);
|
||||
// get void*'s for all entries on the stack
|
||||
size = backtrace(array, 50);
|
||||
|
||||
// print out all the frames to stderr
|
||||
fprintf(stderr, "Error: signal %d:\n", signum);
|
||||
backtrace_symbols_fd(array, size, STDERR_FILENO);
|
||||
// print out all the frames to stderr
|
||||
fprintf(stderr, "Error: signal %d:\n", signum);
|
||||
backtrace_symbols_fd(array, size, STDERR_FILENO);
|
||||
|
||||
int file = ::open("/tmp/nheko-crash.dump",
|
||||
O_CREAT | O_WRONLY | O_TRUNC
|
||||
int file = ::open("/tmp/nheko-crash.dump",
|
||||
O_CREAT | O_WRONLY | O_TRUNC
|
||||
#if defined(S_IWUSR) && defined(S_IRUSR)
|
||||
,
|
||||
S_IWUSR | S_IRUSR
|
||||
,
|
||||
S_IWUSR | S_IRUSR
|
||||
#elif defined(S_IWRITE) && defined(S_IREAD)
|
||||
,
|
||||
S_IWRITE | S_IREAD
|
||||
,
|
||||
S_IWRITE | S_IREAD
|
||||
#endif
|
||||
);
|
||||
if (file != -1) {
|
||||
constexpr char header[] = "Error: signal\n";
|
||||
[[maybe_unused]] auto ret = write(file, header, std::size(header) - 1);
|
||||
backtrace_symbols_fd(array, size, file);
|
||||
close(file);
|
||||
}
|
||||
);
|
||||
if (file != -1) {
|
||||
constexpr char header[] = "Error: signal\n";
|
||||
[[maybe_unused]] auto ret = write(file, header, std::size(header) - 1);
|
||||
backtrace_symbols_fd(array, size, file);
|
||||
close(file);
|
||||
}
|
||||
|
||||
std::raise(SIGABRT);
|
||||
std::raise(SIGABRT);
|
||||
}
|
||||
|
||||
void
|
||||
registerSignalHandlers()
|
||||
{
|
||||
std::signal(SIGSEGV, &stacktraceHandler);
|
||||
std::signal(SIGABRT, &stacktraceHandler);
|
||||
std::signal(SIGSEGV, &stacktraceHandler);
|
||||
std::signal(SIGABRT, &stacktraceHandler);
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
@ -103,203 +103,200 @@ registerSignalHandlers()
|
|||
QPoint
|
||||
screenCenter(int width, int height)
|
||||
{
|
||||
// Deprecated in 5.13: QRect screenGeometry = QApplication::desktop()->screenGeometry();
|
||||
QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
|
||||
// Deprecated in 5.13: QRect screenGeometry = QApplication::desktop()->screenGeometry();
|
||||
QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
|
||||
|
||||
int x = (screenGeometry.width() - width) / 2;
|
||||
int y = (screenGeometry.height() - height) / 2;
|
||||
int x = (screenGeometry.width() - width) / 2;
|
||||
int y = (screenGeometry.height() - height) / 2;
|
||||
|
||||
return QPoint(x, y);
|
||||
return QPoint(x, y);
|
||||
}
|
||||
|
||||
void
|
||||
createStandardDirectory(QStandardPaths::StandardLocation path)
|
||||
{
|
||||
auto dir = QStandardPaths::writableLocation(path);
|
||||
auto dir = QStandardPaths::writableLocation(path);
|
||||
|
||||
if (!QDir().mkpath(dir)) {
|
||||
throw std::runtime_error(
|
||||
("Unable to create state directory:" + dir).toStdString().c_str());
|
||||
}
|
||||
if (!QDir().mkpath(dir)) {
|
||||
throw std::runtime_error(("Unable to create state directory:" + dir).toStdString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication::setApplicationName("nheko");
|
||||
QCoreApplication::setApplicationVersion(nheko::version);
|
||||
QCoreApplication::setOrganizationName("nheko");
|
||||
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
|
||||
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QCoreApplication::setApplicationName("nheko");
|
||||
QCoreApplication::setApplicationVersion(nheko::version);
|
||||
QCoreApplication::setOrganizationName("nheko");
|
||||
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
|
||||
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
|
||||
// this needs to be after setting the application name. Or how would we find our settings
|
||||
// file then?
|
||||
// this needs to be after setting the application name. Or how would we find our settings
|
||||
// file then?
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(Q_OS_FREEBSD)
|
||||
if (qgetenv("QT_SCALE_FACTOR").size() == 0) {
|
||||
float factor = utils::scaleFactor();
|
||||
if (qgetenv("QT_SCALE_FACTOR").size() == 0) {
|
||||
float factor = utils::scaleFactor();
|
||||
|
||||
if (factor != -1)
|
||||
qputenv("QT_SCALE_FACTOR", QString::number(factor).toUtf8());
|
||||
}
|
||||
if (factor != -1)
|
||||
qputenv("QT_SCALE_FACTOR", QString::number(factor).toUtf8());
|
||||
}
|
||||
#endif
|
||||
|
||||
// This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name
|
||||
// parsed before the SingleApplication userdata is set.
|
||||
QString userdata{""};
|
||||
QString matrixUri;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
QString arg{argv[i]};
|
||||
if (arg.startsWith("--profile=")) {
|
||||
arg.remove("--profile=");
|
||||
userdata = arg;
|
||||
} else if (arg.startsWith("--p=")) {
|
||||
arg.remove("-p=");
|
||||
userdata = arg;
|
||||
} else if (arg == "--profile" || arg == "-p") {
|
||||
if (i < argc - 1) // if i is less than argc - 1, we still have a parameter
|
||||
// left to process as the name
|
||||
{
|
||||
++i; // the next arg is the name, so increment
|
||||
userdata = QString{argv[i]};
|
||||
}
|
||||
} else if (arg.startsWith("matrix:")) {
|
||||
matrixUri = arg;
|
||||
}
|
||||
// This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name
|
||||
// parsed before the SingleApplication userdata is set.
|
||||
QString userdata{""};
|
||||
QString matrixUri;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
QString arg{argv[i]};
|
||||
if (arg.startsWith("--profile=")) {
|
||||
arg.remove("--profile=");
|
||||
userdata = arg;
|
||||
} else if (arg.startsWith("--p=")) {
|
||||
arg.remove("-p=");
|
||||
userdata = arg;
|
||||
} else if (arg == "--profile" || arg == "-p") {
|
||||
if (i < argc - 1) // if i is less than argc - 1, we still have a parameter
|
||||
// left to process as the name
|
||||
{
|
||||
++i; // the next arg is the name, so increment
|
||||
userdata = QString{argv[i]};
|
||||
}
|
||||
} else if (arg.startsWith("matrix:")) {
|
||||
matrixUri = arg;
|
||||
}
|
||||
}
|
||||
|
||||
SingleApplication app(argc,
|
||||
argv,
|
||||
true,
|
||||
SingleApplication::Mode::User |
|
||||
SingleApplication::Mode::ExcludeAppPath |
|
||||
SingleApplication::Mode::ExcludeAppVersion |
|
||||
SingleApplication::Mode::SecondaryNotification,
|
||||
100,
|
||||
userdata);
|
||||
SingleApplication app(argc,
|
||||
argv,
|
||||
true,
|
||||
SingleApplication::Mode::User | SingleApplication::Mode::ExcludeAppPath |
|
||||
SingleApplication::Mode::ExcludeAppVersion |
|
||||
SingleApplication::Mode::SecondaryNotification,
|
||||
100,
|
||||
userdata);
|
||||
|
||||
if (app.isSecondary()) {
|
||||
// open uri in main instance
|
||||
app.sendMessage(matrixUri.toUtf8());
|
||||
return 0;
|
||||
if (app.isSecondary()) {
|
||||
// open uri in main instance
|
||||
app.sendMessage(matrixUri.toUtf8());
|
||||
return 0;
|
||||
}
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
QCommandLineOption debugOption("debug", "Enable debug output");
|
||||
parser.addOption(debugOption);
|
||||
|
||||
// This option is not actually parsed via Qt due to the need to parse it before the app
|
||||
// name is set. It only exists to keep Qt from complaining about the --profile/-p
|
||||
// option and thereby crashing the app.
|
||||
QCommandLineOption configName(
|
||||
QStringList() << "p"
|
||||
<< "profile",
|
||||
QCoreApplication::tr("Create a unique profile, which allows you to log into several "
|
||||
"accounts at the same time and start multiple instances of nheko."),
|
||||
QCoreApplication::tr("profile"),
|
||||
QCoreApplication::tr("profile name"));
|
||||
parser.addOption(configName);
|
||||
|
||||
parser.process(app);
|
||||
|
||||
app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"}));
|
||||
|
||||
http::init();
|
||||
|
||||
createStandardDirectory(QStandardPaths::CacheLocation);
|
||||
createStandardDirectory(QStandardPaths::AppDataLocation);
|
||||
|
||||
registerSignalHandlers();
|
||||
|
||||
if (parser.isSet(debugOption))
|
||||
nhlog::enable_debug_log_from_commandline = true;
|
||||
|
||||
try {
|
||||
nhlog::init(QString("%1/nheko.log")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
|
||||
.toStdString());
|
||||
} catch (const spdlog::spdlog_ex &ex) {
|
||||
std::cout << "Log initialization failed: " << ex.what() << std::endl;
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
if (parser.isSet(configName))
|
||||
UserSettings::initialize(parser.value(configName));
|
||||
else
|
||||
UserSettings::initialize(std::nullopt);
|
||||
|
||||
auto settings = UserSettings::instance().toWeakRef();
|
||||
|
||||
QFont font;
|
||||
QString userFontFamily = settings.lock()->font();
|
||||
if (!userFontFamily.isEmpty() && userFontFamily != "default") {
|
||||
font.setFamily(userFontFamily);
|
||||
}
|
||||
font.setPointSizeF(settings.lock()->fontSize());
|
||||
|
||||
app.setFont(font);
|
||||
|
||||
QString lang = QLocale::system().name();
|
||||
|
||||
QTranslator qtTranslator;
|
||||
qtTranslator.load(QLocale(), "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath));
|
||||
app.installTranslator(&qtTranslator);
|
||||
|
||||
QTranslator appTranslator;
|
||||
appTranslator.load(QLocale(), "nheko", "_", ":/translations");
|
||||
app.installTranslator(&appTranslator);
|
||||
|
||||
MainWindow w;
|
||||
|
||||
// Move the MainWindow to the center
|
||||
w.move(screenCenter(w.width(), w.height()));
|
||||
|
||||
if (!(settings.lock()->startInTray() && settings.lock()->tray()))
|
||||
w.show();
|
||||
|
||||
QObject::connect(&app, &QApplication::aboutToQuit, &w, [&w]() {
|
||||
w.saveCurrentWindowSize();
|
||||
if (http::client() != nullptr) {
|
||||
nhlog::net()->debug("shutting down all I/O threads & open connections");
|
||||
http::client()->close(true);
|
||||
nhlog::net()->debug("bye");
|
||||
}
|
||||
});
|
||||
QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() {
|
||||
w.show();
|
||||
w.raise();
|
||||
w.activateWindow();
|
||||
});
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
QCommandLineOption debugOption("debug", "Enable debug output");
|
||||
parser.addOption(debugOption);
|
||||
QObject::connect(
|
||||
&app,
|
||||
&SingleApplication::receivedMessage,
|
||||
ChatPage::instance(),
|
||||
[&](quint32, QByteArray message) { ChatPage::instance()->handleMatrixUri(message); });
|
||||
|
||||
// This option is not actually parsed via Qt due to the need to parse it before the app
|
||||
// name is set. It only exists to keep Qt from complaining about the --profile/-p
|
||||
// option and thereby crashing the app.
|
||||
QCommandLineOption configName(
|
||||
QStringList() << "p"
|
||||
<< "profile",
|
||||
QCoreApplication::tr("Create a unique profile, which allows you to log into several "
|
||||
"accounts at the same time and start multiple instances of nheko."),
|
||||
QCoreApplication::tr("profile"),
|
||||
QCoreApplication::tr("profile name"));
|
||||
parser.addOption(configName);
|
||||
|
||||
parser.process(app);
|
||||
|
||||
app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"}));
|
||||
|
||||
http::init();
|
||||
|
||||
createStandardDirectory(QStandardPaths::CacheLocation);
|
||||
createStandardDirectory(QStandardPaths::AppDataLocation);
|
||||
|
||||
registerSignalHandlers();
|
||||
|
||||
if (parser.isSet(debugOption))
|
||||
nhlog::enable_debug_log_from_commandline = true;
|
||||
|
||||
try {
|
||||
nhlog::init(QString("%1/nheko.log")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
|
||||
.toStdString());
|
||||
} catch (const spdlog::spdlog_ex &ex) {
|
||||
std::cout << "Log initialization failed: " << ex.what() << std::endl;
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
if (parser.isSet(configName))
|
||||
UserSettings::initialize(parser.value(configName));
|
||||
else
|
||||
UserSettings::initialize(std::nullopt);
|
||||
|
||||
auto settings = UserSettings::instance().toWeakRef();
|
||||
|
||||
QFont font;
|
||||
QString userFontFamily = settings.lock()->font();
|
||||
if (!userFontFamily.isEmpty() && userFontFamily != "default") {
|
||||
font.setFamily(userFontFamily);
|
||||
}
|
||||
font.setPointSizeF(settings.lock()->fontSize());
|
||||
|
||||
app.setFont(font);
|
||||
|
||||
QString lang = QLocale::system().name();
|
||||
|
||||
QTranslator qtTranslator;
|
||||
qtTranslator.load(
|
||||
QLocale(), "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath));
|
||||
app.installTranslator(&qtTranslator);
|
||||
|
||||
QTranslator appTranslator;
|
||||
appTranslator.load(QLocale(), "nheko", "_", ":/translations");
|
||||
app.installTranslator(&appTranslator);
|
||||
|
||||
MainWindow w;
|
||||
|
||||
// Move the MainWindow to the center
|
||||
w.move(screenCenter(w.width(), w.height()));
|
||||
|
||||
if (!(settings.lock()->startInTray() && settings.lock()->tray()))
|
||||
w.show();
|
||||
|
||||
QObject::connect(&app, &QApplication::aboutToQuit, &w, [&w]() {
|
||||
w.saveCurrentWindowSize();
|
||||
if (http::client() != nullptr) {
|
||||
nhlog::net()->debug("shutting down all I/O threads & open connections");
|
||||
http::client()->close(true);
|
||||
nhlog::net()->debug("bye");
|
||||
}
|
||||
});
|
||||
QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() {
|
||||
w.show();
|
||||
w.raise();
|
||||
w.activateWindow();
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
&app,
|
||||
&SingleApplication::receivedMessage,
|
||||
ChatPage::instance(),
|
||||
[&](quint32, QByteArray message) { ChatPage::instance()->handleMatrixUri(message); });
|
||||
|
||||
QMetaObject::Connection uriConnection;
|
||||
if (app.isPrimary() && !matrixUri.isEmpty()) {
|
||||
uriConnection = QObject::connect(ChatPage::instance(),
|
||||
&ChatPage::contentLoaded,
|
||||
ChatPage::instance(),
|
||||
[&uriConnection, matrixUri]() {
|
||||
ChatPage::instance()->handleMatrixUri(
|
||||
matrixUri.toUtf8());
|
||||
QObject::disconnect(uriConnection);
|
||||
});
|
||||
}
|
||||
QDesktopServices::setUrlHandler("matrix", ChatPage::instance(), "handleMatrixUri");
|
||||
QMetaObject::Connection uriConnection;
|
||||
if (app.isPrimary() && !matrixUri.isEmpty()) {
|
||||
uriConnection =
|
||||
QObject::connect(ChatPage::instance(),
|
||||
&ChatPage::contentLoaded,
|
||||
ChatPage::instance(),
|
||||
[&uriConnection, matrixUri]() {
|
||||
ChatPage::instance()->handleMatrixUri(matrixUri.toUtf8());
|
||||
QObject::disconnect(uriConnection);
|
||||
});
|
||||
}
|
||||
QDesktopServices::setUrlHandler("matrix", ChatPage::instance(), "handleMatrixUri");
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
// Temporary solution for the emoji picker until
|
||||
// nheko has a proper menu bar with more functionality.
|
||||
MacHelper::initializeMenus();
|
||||
// Temporary solution for the emoji picker until
|
||||
// nheko has a proper menu bar with more functionality.
|
||||
MacHelper::initializeMenus();
|
||||
#endif
|
||||
|
||||
nhlog::ui()->info("starting nheko {}", nheko::version);
|
||||
nhlog::ui()->info("starting nheko {}", nheko::version);
|
||||
|
||||
return app.exec();
|
||||
return app.exec();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,30 +11,30 @@
|
|||
QString
|
||||
NotificationsManager::getMessageTemplate(const mtx::responses::Notification ¬ification)
|
||||
{
|
||||
const auto sender =
|
||||
cache::displayName(QString::fromStdString(notification.room_id),
|
||||
QString::fromStdString(mtx::accessors::sender(notification.event)));
|
||||
const auto sender =
|
||||
cache::displayName(QString::fromStdString(notification.room_id),
|
||||
QString::fromStdString(mtx::accessors::sender(notification.event)));
|
||||
|
||||
// TODO: decrypt this message if the decryption setting is on in the UserSettings
|
||||
if (auto msg = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||
¬ification.event);
|
||||
msg != nullptr) {
|
||||
return tr("%1 sent an encrypted message").arg(sender);
|
||||
}
|
||||
// TODO: decrypt this message if the decryption setting is on in the UserSettings
|
||||
if (auto msg = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||
¬ification.event);
|
||||
msg != nullptr) {
|
||||
return tr("%1 sent an encrypted message").arg(sender);
|
||||
}
|
||||
|
||||
if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) {
|
||||
return tr("* %1 %2",
|
||||
"Format an emote message in a notification, %1 is the sender, %2 the "
|
||||
"message")
|
||||
.arg(sender);
|
||||
} else if (utils::isReply(notification.event)) {
|
||||
return tr("%1 replied: %2",
|
||||
"Format a reply in a notification. %1 is the sender, %2 the message")
|
||||
.arg(sender);
|
||||
} else {
|
||||
return tr("%1: %2",
|
||||
"Format a normal message in a notification. %1 is the sender, %2 the "
|
||||
"message")
|
||||
.arg(sender);
|
||||
}
|
||||
if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) {
|
||||
return tr("* %1 %2",
|
||||
"Format an emote message in a notification, %1 is the sender, %2 the "
|
||||
"message")
|
||||
.arg(sender);
|
||||
} else if (utils::isReply(notification.event)) {
|
||||
return tr("%1 replied: %2",
|
||||
"Format a reply in a notification. %1 is the sender, %2 the message")
|
||||
.arg(sender);
|
||||
} else {
|
||||
return tr("%1: %2",
|
||||
"Format a normal message in a notification. %1 is the sender, %2 the "
|
||||
"message")
|
||||
.arg(sender);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,83 +22,83 @@
|
|||
|
||||
struct roomEventId
|
||||
{
|
||||
QString roomId;
|
||||
QString eventId;
|
||||
QString roomId;
|
||||
QString eventId;
|
||||
};
|
||||
|
||||
inline bool
|
||||
operator==(const roomEventId &a, const roomEventId &b)
|
||||
{
|
||||
return a.roomId == b.roomId && a.eventId == b.eventId;
|
||||
return a.roomId == b.roomId && a.eventId == b.eventId;
|
||||
}
|
||||
|
||||
class NotificationsManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
NotificationsManager(QObject *parent = nullptr);
|
||||
NotificationsManager(QObject *parent = nullptr);
|
||||
|
||||
void postNotification(const mtx::responses::Notification ¬ification, const QImage &icon);
|
||||
void postNotification(const mtx::responses::Notification ¬ification, const QImage &icon);
|
||||
|
||||
signals:
|
||||
void notificationClicked(const QString roomId, const QString eventId);
|
||||
void sendNotificationReply(const QString roomId, const QString eventId, const QString body);
|
||||
void systemPostNotificationCb(const QString &room_id,
|
||||
const QString &event_id,
|
||||
const QString &roomName,
|
||||
const QString &text,
|
||||
const QImage &icon);
|
||||
void notificationClicked(const QString roomId, const QString eventId);
|
||||
void sendNotificationReply(const QString roomId, const QString eventId, const QString body);
|
||||
void systemPostNotificationCb(const QString &room_id,
|
||||
const QString &event_id,
|
||||
const QString &roomName,
|
||||
const QString &text,
|
||||
const QImage &icon);
|
||||
|
||||
public slots:
|
||||
void removeNotification(const QString &roomId, const QString &eventId);
|
||||
void removeNotification(const QString &roomId, const QString &eventId);
|
||||
|
||||
#if defined(NHEKO_DBUS_SYS)
|
||||
public:
|
||||
void closeNotifications(QString roomId);
|
||||
void closeNotifications(QString roomId);
|
||||
|
||||
private:
|
||||
QDBusInterface dbus;
|
||||
QDBusInterface dbus;
|
||||
|
||||
void systemPostNotification(const QString &room_id,
|
||||
const QString &event_id,
|
||||
const QString &roomName,
|
||||
const QString &text,
|
||||
const QImage &icon);
|
||||
void closeNotification(uint id);
|
||||
void systemPostNotification(const QString &room_id,
|
||||
const QString &event_id,
|
||||
const QString &roomName,
|
||||
const QString &text,
|
||||
const QImage &icon);
|
||||
void closeNotification(uint id);
|
||||
|
||||
// notification ID to (room ID, event ID)
|
||||
QMap<uint, roomEventId> notificationIds;
|
||||
// notification ID to (room ID, event ID)
|
||||
QMap<uint, roomEventId> notificationIds;
|
||||
|
||||
const bool hasMarkup_;
|
||||
const bool hasImages_;
|
||||
const bool hasMarkup_;
|
||||
const bool hasImages_;
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
private:
|
||||
// Objective-C(++) doesn't like to do lots of regular C++, so the actual notification
|
||||
// posting is split out
|
||||
void objCxxPostNotification(const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &informativeText,
|
||||
const QImage &bodyImage);
|
||||
// Objective-C(++) doesn't like to do lots of regular C++, so the actual notification
|
||||
// posting is split out
|
||||
void objCxxPostNotification(const QString &title,
|
||||
const QString &subtitle,
|
||||
const QString &informativeText,
|
||||
const QImage &bodyImage);
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
private:
|
||||
void systemPostNotification(const QString &line1,
|
||||
const QString &line2,
|
||||
const QString &iconPath);
|
||||
void systemPostNotification(const QString &line1,
|
||||
const QString &line2,
|
||||
const QString &iconPath);
|
||||
#endif
|
||||
|
||||
// these slots are platform specific (D-Bus only)
|
||||
// but Qt slot declarations can not be inside an ifdef!
|
||||
// these slots are platform specific (D-Bus only)
|
||||
// but Qt slot declarations can not be inside an ifdef!
|
||||
private slots:
|
||||
void actionInvoked(uint id, QString action);
|
||||
void notificationClosed(uint id, uint reason);
|
||||
void notificationReplied(uint id, QString reply);
|
||||
void actionInvoked(uint id, QString action);
|
||||
void notificationClosed(uint id, uint reason);
|
||||
void notificationReplied(uint id, QString reply);
|
||||
|
||||
private:
|
||||
QString getMessageTemplate(const mtx::responses::Notification ¬ification);
|
||||
QString getMessageTemplate(const mtx::responses::Notification ¬ification);
|
||||
};
|
||||
|
||||
#if defined(NHEKO_DBUS_SYS)
|
||||
|
|
|
|||
|
|
@ -34,109 +34,100 @@ NotificationsManager::NotificationsManager(QObject *parent)
|
|||
QDBusConnection::sessionBus(),
|
||||
this)
|
||||
, hasMarkup_{std::invoke([this]() -> bool {
|
||||
for (auto x : dbus.call("GetCapabilities").arguments())
|
||||
if (x.toStringList().contains("body-markup"))
|
||||
return true;
|
||||
return false;
|
||||
for (auto x : dbus.call("GetCapabilities").arguments())
|
||||
if (x.toStringList().contains("body-markup"))
|
||||
return true;
|
||||
return false;
|
||||
})}
|
||||
, hasImages_{std::invoke([this]() -> bool {
|
||||
for (auto x : dbus.call("GetCapabilities").arguments())
|
||||
if (x.toStringList().contains("body-images"))
|
||||
return true;
|
||||
return false;
|
||||
for (auto x : dbus.call("GetCapabilities").arguments())
|
||||
if (x.toStringList().contains("body-images"))
|
||||
return true;
|
||||
return false;
|
||||
})}
|
||||
{
|
||||
qDBusRegisterMetaType<QImage>();
|
||||
qDBusRegisterMetaType<QImage>();
|
||||
|
||||
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
"ActionInvoked",
|
||||
this,
|
||||
SLOT(actionInvoked(uint, QString)));
|
||||
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
"NotificationClosed",
|
||||
this,
|
||||
SLOT(notificationClosed(uint, uint)));
|
||||
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
"NotificationReplied",
|
||||
this,
|
||||
SLOT(notificationReplied(uint, QString)));
|
||||
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
"ActionInvoked",
|
||||
this,
|
||||
SLOT(actionInvoked(uint, QString)));
|
||||
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
"NotificationClosed",
|
||||
this,
|
||||
SLOT(notificationClosed(uint, uint)));
|
||||
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
"NotificationReplied",
|
||||
this,
|
||||
SLOT(notificationReplied(uint, QString)));
|
||||
|
||||
connect(this,
|
||||
&NotificationsManager::systemPostNotificationCb,
|
||||
this,
|
||||
&NotificationsManager::systemPostNotification,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&NotificationsManager::systemPostNotificationCb,
|
||||
this,
|
||||
&NotificationsManager::systemPostNotification,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void
|
||||
NotificationsManager::postNotification(const mtx::responses::Notification ¬ification,
|
||||
const QImage &icon)
|
||||
{
|
||||
const auto room_id = QString::fromStdString(notification.room_id);
|
||||
const auto event_id = QString::fromStdString(mtx::accessors::event_id(notification.event));
|
||||
const auto room_name =
|
||||
QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
|
||||
const auto room_id = QString::fromStdString(notification.room_id);
|
||||
const auto event_id = QString::fromStdString(mtx::accessors::event_id(notification.event));
|
||||
const auto room_name = QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
|
||||
|
||||
auto postNotif = [this, room_id, event_id, room_name, icon](QString text) {
|
||||
emit systemPostNotificationCb(room_id, event_id, room_name, text, icon);
|
||||
};
|
||||
auto postNotif = [this, room_id, event_id, room_name, icon](QString text) {
|
||||
emit systemPostNotificationCb(room_id, event_id, room_name, text, icon);
|
||||
};
|
||||
|
||||
QString template_ = getMessageTemplate(notification);
|
||||
// TODO: decrypt this message if the decryption setting is on in the UserSettings
|
||||
if (std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||
notification.event)) {
|
||||
postNotif(template_);
|
||||
return;
|
||||
}
|
||||
QString template_ = getMessageTemplate(notification);
|
||||
// TODO: decrypt this message if the decryption setting is on in the UserSettings
|
||||
if (std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||
notification.event)) {
|
||||
postNotif(template_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasMarkup_) {
|
||||
if (hasImages_ && mtx::accessors::msg_type(notification.event) ==
|
||||
mtx::events::MessageType::Image) {
|
||||
MxcImageProvider::download(
|
||||
QString::fromStdString(mtx::accessors::url(notification.event))
|
||||
.remove("mxc://"),
|
||||
QSize(200, 80),
|
||||
[postNotif, notification, template_](
|
||||
QString, QSize, QImage, QString imgPath) {
|
||||
if (imgPath.isEmpty())
|
||||
postNotif(template_
|
||||
.arg(utils::stripReplyFallbacks(
|
||||
notification.event, {}, {})
|
||||
.quoted_formatted_body)
|
||||
.replace("<em>", "<i>")
|
||||
.replace("</em>", "</i>")
|
||||
.replace("<strong>", "<b>")
|
||||
.replace("</strong>", "</b>"));
|
||||
else
|
||||
postNotif(template_.arg(
|
||||
QStringLiteral("<br><img src=\"file:///") % imgPath %
|
||||
"\" alt=\"" %
|
||||
mtx::accessors::formattedBodyWithFallback(
|
||||
notification.event) %
|
||||
"\">"));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
postNotif(
|
||||
template_
|
||||
.arg(
|
||||
utils::stripReplyFallbacks(notification.event, {}, {}).quoted_formatted_body)
|
||||
.replace("<em>", "<i>")
|
||||
.replace("</em>", "</i>")
|
||||
.replace("<strong>", "<b>")
|
||||
.replace("</strong>", "</b>"));
|
||||
return;
|
||||
if (hasMarkup_) {
|
||||
if (hasImages_ &&
|
||||
mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image) {
|
||||
MxcImageProvider::download(
|
||||
QString::fromStdString(mtx::accessors::url(notification.event)).remove("mxc://"),
|
||||
QSize(200, 80),
|
||||
[postNotif, notification, template_](QString, QSize, QImage, QString imgPath) {
|
||||
if (imgPath.isEmpty())
|
||||
postNotif(template_
|
||||
.arg(utils::stripReplyFallbacks(notification.event, {}, {})
|
||||
.quoted_formatted_body)
|
||||
.replace("<em>", "<i>")
|
||||
.replace("</em>", "</i>")
|
||||
.replace("<strong>", "<b>")
|
||||
.replace("</strong>", "</b>"));
|
||||
else
|
||||
postNotif(template_.arg(
|
||||
QStringLiteral("<br><img src=\"file:///") % imgPath % "\" alt=\"" %
|
||||
mtx::accessors::formattedBodyWithFallback(notification.event) % "\">"));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
postNotif(
|
||||
template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body));
|
||||
template_
|
||||
.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_formatted_body)
|
||||
.replace("<em>", "<i>")
|
||||
.replace("</em>", "</i>")
|
||||
.replace("<strong>", "<b>")
|
||||
.replace("</strong>", "</b>"));
|
||||
return;
|
||||
}
|
||||
|
||||
postNotif(template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -152,99 +143,99 @@ NotificationsManager::systemPostNotification(const QString &room_id,
|
|||
const QString &text,
|
||||
const QImage &icon)
|
||||
{
|
||||
QVariantMap hints;
|
||||
hints["image-data"] = icon;
|
||||
hints["sound-name"] = "message-new-instant";
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << "nheko"; // app_name
|
||||
argumentList << (uint)0; // replace_id
|
||||
argumentList << ""; // app_icon
|
||||
argumentList << roomName; // summary
|
||||
argumentList << text; // body
|
||||
QVariantMap hints;
|
||||
hints["image-data"] = icon;
|
||||
hints["sound-name"] = "message-new-instant";
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << "nheko"; // app_name
|
||||
argumentList << (uint)0; // replace_id
|
||||
argumentList << ""; // app_icon
|
||||
argumentList << roomName; // summary
|
||||
argumentList << text; // body
|
||||
|
||||
// The list of actions has always the action name and then a localized version of that
|
||||
// action. Currently we just use an empty string for that.
|
||||
// TODO(Nico): Look into what to actually put there.
|
||||
argumentList << (QStringList("default") << ""
|
||||
<< "inline-reply"
|
||||
<< ""); // actions
|
||||
argumentList << hints; // hints
|
||||
argumentList << (int)-1; // timeout in ms
|
||||
// The list of actions has always the action name and then a localized version of that
|
||||
// action. Currently we just use an empty string for that.
|
||||
// TODO(Nico): Look into what to actually put there.
|
||||
argumentList << (QStringList("default") << ""
|
||||
<< "inline-reply"
|
||||
<< ""); // actions
|
||||
argumentList << hints; // hints
|
||||
argumentList << (int)-1; // timeout in ms
|
||||
|
||||
QDBusPendingCall call = dbus.asyncCallWithArgumentList("Notify", argumentList);
|
||||
auto watcher = new QDBusPendingCallWatcher{call, this};
|
||||
connect(
|
||||
watcher, &QDBusPendingCallWatcher::finished, this, [watcher, this, room_id, event_id]() {
|
||||
if (watcher->reply().type() == QDBusMessage::ErrorMessage)
|
||||
qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
|
||||
else
|
||||
notificationIds[watcher->reply().arguments().first().toUInt()] =
|
||||
roomEventId{room_id, event_id};
|
||||
watcher->deleteLater();
|
||||
});
|
||||
QDBusPendingCall call = dbus.asyncCallWithArgumentList("Notify", argumentList);
|
||||
auto watcher = new QDBusPendingCallWatcher{call, this};
|
||||
connect(
|
||||
watcher, &QDBusPendingCallWatcher::finished, this, [watcher, this, room_id, event_id]() {
|
||||
if (watcher->reply().type() == QDBusMessage::ErrorMessage)
|
||||
qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
|
||||
else
|
||||
notificationIds[watcher->reply().arguments().first().toUInt()] =
|
||||
roomEventId{room_id, event_id};
|
||||
watcher->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
NotificationsManager::closeNotification(uint id)
|
||||
{
|
||||
auto call = dbus.asyncCall("CloseNotification", (uint)id); // replace_id
|
||||
auto watcher = new QDBusPendingCallWatcher{call, this};
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [watcher]() {
|
||||
if (watcher->reply().type() == QDBusMessage::ErrorMessage) {
|
||||
qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
|
||||
};
|
||||
watcher->deleteLater();
|
||||
});
|
||||
auto call = dbus.asyncCall("CloseNotification", (uint)id); // replace_id
|
||||
auto watcher = new QDBusPendingCallWatcher{call, this};
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [watcher]() {
|
||||
if (watcher->reply().type() == QDBusMessage::ErrorMessage) {
|
||||
qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
|
||||
};
|
||||
watcher->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
NotificationsManager::removeNotification(const QString &roomId, const QString &eventId)
|
||||
{
|
||||
roomEventId reId = {roomId, eventId};
|
||||
for (auto elem = notificationIds.begin(); elem != notificationIds.end(); ++elem) {
|
||||
if (elem.value().roomId != roomId)
|
||||
continue;
|
||||
roomEventId reId = {roomId, eventId};
|
||||
for (auto elem = notificationIds.begin(); elem != notificationIds.end(); ++elem) {
|
||||
if (elem.value().roomId != roomId)
|
||||
continue;
|
||||
|
||||
// close all notifications matching the eventId or having a lower
|
||||
// notificationId
|
||||
// This relies on the notificationId not wrapping around. This allows for
|
||||
// approximately 2,147,483,647 notifications, so it is a bit unlikely.
|
||||
// Otherwise we would need to store a 64bit counter instead.
|
||||
closeNotification(elem.key());
|
||||
// close all notifications matching the eventId or having a lower
|
||||
// notificationId
|
||||
// This relies on the notificationId not wrapping around. This allows for
|
||||
// approximately 2,147,483,647 notifications, so it is a bit unlikely.
|
||||
// Otherwise we would need to store a 64bit counter instead.
|
||||
closeNotification(elem.key());
|
||||
|
||||
// FIXME: compare index of event id of the read receipt and the notification instead
|
||||
// of just the id to prevent read receipts of events without notification clearing
|
||||
// all notifications in that room!
|
||||
if (elem.value() == reId)
|
||||
break;
|
||||
}
|
||||
// FIXME: compare index of event id of the read receipt and the notification instead
|
||||
// of just the id to prevent read receipts of events without notification clearing
|
||||
// all notifications in that room!
|
||||
if (elem.value() == reId)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
NotificationsManager::actionInvoked(uint id, QString action)
|
||||
{
|
||||
if (notificationIds.contains(id)) {
|
||||
roomEventId idEntry = notificationIds[id];
|
||||
if (action == "default") {
|
||||
emit notificationClicked(idEntry.roomId, idEntry.eventId);
|
||||
}
|
||||
if (notificationIds.contains(id)) {
|
||||
roomEventId idEntry = notificationIds[id];
|
||||
if (action == "default") {
|
||||
emit notificationClicked(idEntry.roomId, idEntry.eventId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
NotificationsManager::notificationReplied(uint id, QString reply)
|
||||
{
|
||||
if (notificationIds.contains(id)) {
|
||||
roomEventId idEntry = notificationIds[id];
|
||||
emit sendNotificationReply(idEntry.roomId, idEntry.eventId, reply);
|
||||
}
|
||||
if (notificationIds.contains(id)) {
|
||||
roomEventId idEntry = notificationIds[id];
|
||||
emit sendNotificationReply(idEntry.roomId, idEntry.eventId, reply);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
NotificationsManager::notificationClosed(uint id, uint reason)
|
||||
{
|
||||
Q_UNUSED(reason);
|
||||
notificationIds.remove(id);
|
||||
Q_UNUSED(reason);
|
||||
notificationIds.remove(id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -259,52 +250,52 @@ NotificationsManager::notificationClosed(uint id, uint reason)
|
|||
QDBusArgument &
|
||||
operator<<(QDBusArgument &arg, const QImage &image)
|
||||
{
|
||||
if (image.isNull()) {
|
||||
arg.beginStructure();
|
||||
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
|
||||
arg.endStructure();
|
||||
return arg;
|
||||
}
|
||||
if (image.isNull()) {
|
||||
arg.beginStructure();
|
||||
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
|
||||
arg.endStructure();
|
||||
return arg;
|
||||
}
|
||||
|
||||
QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
|
||||
scaled = scaled.convertToFormat(QImage::Format_ARGB32);
|
||||
QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
|
||||
scaled = scaled.convertToFormat(QImage::Format_ARGB32);
|
||||
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
// ABGR -> ARGB
|
||||
QImage i = scaled.rgbSwapped();
|
||||
// ABGR -> ARGB
|
||||
QImage i = scaled.rgbSwapped();
|
||||
#else
|
||||
// ABGR -> GBAR
|
||||
QImage i(scaled.size(), scaled.format());
|
||||
for (int y = 0; y < i.height(); ++y) {
|
||||
QRgb *p = (QRgb *)scaled.scanLine(y);
|
||||
QRgb *q = (QRgb *)i.scanLine(y);
|
||||
QRgb *end = p + scaled.width();
|
||||
while (p < end) {
|
||||
*q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
|
||||
p++;
|
||||
q++;
|
||||
}
|
||||
// ABGR -> GBAR
|
||||
QImage i(scaled.size(), scaled.format());
|
||||
for (int y = 0; y < i.height(); ++y) {
|
||||
QRgb *p = (QRgb *)scaled.scanLine(y);
|
||||
QRgb *q = (QRgb *)i.scanLine(y);
|
||||
QRgb *end = p + scaled.width();
|
||||
while (p < end) {
|
||||
*q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
|
||||
p++;
|
||||
q++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
arg.beginStructure();
|
||||
arg << i.width();
|
||||
arg << i.height();
|
||||
arg << i.bytesPerLine();
|
||||
arg << i.hasAlphaChannel();
|
||||
int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
|
||||
arg << i.depth() / channels;
|
||||
arg << channels;
|
||||
arg << QByteArray(reinterpret_cast<const char *>(i.bits()), i.sizeInBytes());
|
||||
arg.endStructure();
|
||||
arg.beginStructure();
|
||||
arg << i.width();
|
||||
arg << i.height();
|
||||
arg << i.bytesPerLine();
|
||||
arg << i.hasAlphaChannel();
|
||||
int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
|
||||
arg << i.depth() / channels;
|
||||
arg << channels;
|
||||
arg << QByteArray(reinterpret_cast<const char *>(i.bits()), i.sizeInBytes());
|
||||
arg.endStructure();
|
||||
|
||||
return arg;
|
||||
return arg;
|
||||
}
|
||||
|
||||
const QDBusArgument &
|
||||
operator>>(const QDBusArgument &arg, QImage &)
|
||||
{
|
||||
// This is needed to link but shouldn't be called.
|
||||
Q_ASSERT(0);
|
||||
return arg;
|
||||
// This is needed to link but shouldn't be called.
|
||||
Q_ASSERT(0);
|
||||
return arg;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,48 +19,42 @@
|
|||
static QString
|
||||
formatNotification(const mtx::responses::Notification ¬ification)
|
||||
{
|
||||
return utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body;
|
||||
return utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body;
|
||||
}
|
||||
|
||||
void
|
||||
NotificationsManager::postNotification(const mtx::responses::Notification ¬ification,
|
||||
const QImage &icon)
|
||||
{
|
||||
Q_UNUSED(icon)
|
||||
Q_UNUSED(icon)
|
||||
|
||||
const auto room_name =
|
||||
QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
|
||||
const auto sender =
|
||||
cache::displayName(QString::fromStdString(notification.room_id),
|
||||
QString::fromStdString(mtx::accessors::sender(notification.event)));
|
||||
const auto room_name = QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
|
||||
const auto sender =
|
||||
cache::displayName(QString::fromStdString(notification.room_id),
|
||||
QString::fromStdString(mtx::accessors::sender(notification.event)));
|
||||
|
||||
const auto isEncrypted =
|
||||
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||
¬ification.event) != nullptr;
|
||||
const auto isReply = utils::isReply(notification.event);
|
||||
if (isEncrypted) {
|
||||
// TODO: decrypt this message if the decryption setting is on in the UserSettings
|
||||
const QString messageInfo = (isReply ? tr("%1 replied with an encrypted message")
|
||||
: tr("%1 sent an encrypted message"))
|
||||
.arg(sender);
|
||||
objCxxPostNotification(room_name, messageInfo, "", QImage());
|
||||
} else {
|
||||
const QString messageInfo =
|
||||
(isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender);
|
||||
if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image)
|
||||
MxcImageProvider::download(
|
||||
QString::fromStdString(mtx::accessors::url(notification.event))
|
||||
.remove("mxc://"),
|
||||
QSize(200, 80),
|
||||
[this, notification, room_name, messageInfo](
|
||||
QString, QSize, QImage image, QString) {
|
||||
objCxxPostNotification(room_name,
|
||||
messageInfo,
|
||||
formatNotification(notification),
|
||||
image);
|
||||
});
|
||||
else
|
||||
objCxxPostNotification(
|
||||
room_name, messageInfo, formatNotification(notification), QImage());
|
||||
}
|
||||
const auto isEncrypted = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||
¬ification.event) != nullptr;
|
||||
const auto isReply = utils::isReply(notification.event);
|
||||
if (isEncrypted) {
|
||||
// TODO: decrypt this message if the decryption setting is on in the UserSettings
|
||||
const QString messageInfo = (isReply ? tr("%1 replied with an encrypted message")
|
||||
: tr("%1 sent an encrypted message"))
|
||||
.arg(sender);
|
||||
objCxxPostNotification(room_name, messageInfo, "", QImage());
|
||||
} else {
|
||||
const QString messageInfo =
|
||||
(isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender);
|
||||
if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image)
|
||||
MxcImageProvider::download(
|
||||
QString::fromStdString(mtx::accessors::url(notification.event)).remove("mxc://"),
|
||||
QSize(200, 80),
|
||||
[this, notification, room_name, messageInfo](QString, QSize, QImage image, QString) {
|
||||
objCxxPostNotification(
|
||||
room_name, messageInfo, formatNotification(notification), image);
|
||||
});
|
||||
else
|
||||
objCxxPostNotification(
|
||||
room_name, messageInfo, formatNotification(notification), QImage());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ using namespace WinToastLib;
|
|||
class CustomHandler : public IWinToastHandler
|
||||
{
|
||||
public:
|
||||
void toastActivated() const {}
|
||||
void toastActivated(int) const {}
|
||||
void toastFailed() const { std::wcout << L"Error showing current toast" << std::endl; }
|
||||
void toastDismissed(WinToastDismissalReason) const {}
|
||||
void toastActivated() const {}
|
||||
void toastActivated(int) const {}
|
||||
void toastFailed() const { std::wcout << L"Error showing current toast" << std::endl; }
|
||||
void toastDismissed(WinToastDismissalReason) const {}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
|
@ -32,12 +32,12 @@ bool isInitialized = false;
|
|||
void
|
||||
init()
|
||||
{
|
||||
isInitialized = true;
|
||||
isInitialized = true;
|
||||
|
||||
WinToast::instance()->setAppName(L"Nheko");
|
||||
WinToast::instance()->setAppUserModelId(WinToast::configureAUMI(L"nheko", L"nheko"));
|
||||
if (!WinToast::instance()->initialize())
|
||||
std::wcout << "Your system is not compatible with toast notifications\n";
|
||||
WinToast::instance()->setAppName(L"Nheko");
|
||||
WinToast::instance()->setAppUserModelId(WinToast::configureAUMI(L"nheko", L"nheko"));
|
||||
if (!WinToast::instance()->initialize())
|
||||
std::wcout << "Your system is not compatible with toast notifications\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49,41 +49,37 @@ void
|
|||
NotificationsManager::postNotification(const mtx::responses::Notification ¬ification,
|
||||
const QImage &icon)
|
||||
{
|
||||
const auto room_name =
|
||||
QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
|
||||
const auto sender =
|
||||
cache::displayName(QString::fromStdString(notification.room_id),
|
||||
QString::fromStdString(mtx::accessors::sender(notification.event)));
|
||||
const auto room_name = QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
|
||||
const auto sender =
|
||||
cache::displayName(QString::fromStdString(notification.room_id),
|
||||
QString::fromStdString(mtx::accessors::sender(notification.event)));
|
||||
|
||||
const auto isEncrypted =
|
||||
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||
¬ification.event) != nullptr;
|
||||
const auto isReply = utils::isReply(notification.event);
|
||||
const auto isEncrypted = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||
¬ification.event) != nullptr;
|
||||
const auto isReply = utils::isReply(notification.event);
|
||||
|
||||
auto formatNotification = [this, notification, sender] {
|
||||
const auto template_ = getMessageTemplate(notification);
|
||||
if (std::holds_alternative<
|
||||
mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||
notification.event)) {
|
||||
return template_;
|
||||
}
|
||||
auto formatNotification = [this, notification, sender] {
|
||||
const auto template_ = getMessageTemplate(notification);
|
||||
if (std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
|
||||
notification.event)) {
|
||||
return template_;
|
||||
}
|
||||
|
||||
return template_.arg(
|
||||
utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body);
|
||||
};
|
||||
return template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body);
|
||||
};
|
||||
|
||||
const auto line1 =
|
||||
(room_name == sender) ? sender : QString("%1 - %2").arg(sender).arg(room_name);
|
||||
const auto line2 = (isEncrypted ? (isReply ? tr("%1 replied with an encrypted message")
|
||||
: tr("%1 sent an encrypted message"))
|
||||
: formatNotification());
|
||||
const auto line1 =
|
||||
(room_name == sender) ? sender : QString("%1 - %2").arg(sender).arg(room_name);
|
||||
const auto line2 = (isEncrypted ? (isReply ? tr("%1 replied with an encrypted message")
|
||||
: tr("%1 sent an encrypted message"))
|
||||
: formatNotification());
|
||||
|
||||
auto iconPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||
room_name + "-room-avatar.png";
|
||||
if (!icon.save(iconPath))
|
||||
iconPath.clear();
|
||||
auto iconPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + room_name +
|
||||
"-room-avatar.png";
|
||||
if (!icon.save(iconPath))
|
||||
iconPath.clear();
|
||||
|
||||
systemPostNotification(line1, line2, iconPath);
|
||||
systemPostNotification(line1, line2, iconPath);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -91,17 +87,17 @@ NotificationsManager::systemPostNotification(const QString &line1,
|
|||
const QString &line2,
|
||||
const QString &iconPath)
|
||||
{
|
||||
if (!isInitialized)
|
||||
init();
|
||||
if (!isInitialized)
|
||||
init();
|
||||
|
||||
auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
|
||||
templ.setTextField(line1.toStdWString(), WinToastTemplate::FirstLine);
|
||||
templ.setTextField(line2.toStdWString(), WinToastTemplate::SecondLine);
|
||||
auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
|
||||
templ.setTextField(line1.toStdWString(), WinToastTemplate::FirstLine);
|
||||
templ.setTextField(line2.toStdWString(), WinToastTemplate::SecondLine);
|
||||
|
||||
if (!iconPath.isNull())
|
||||
templ.setImagePath(iconPath.toStdWString());
|
||||
if (!iconPath.isNull())
|
||||
templ.setImagePath(iconPath.toStdWString());
|
||||
|
||||
WinToast::instance()->showToast(templ, new CustomHandler());
|
||||
WinToast::instance()->showToast(templ, new CustomHandler());
|
||||
}
|
||||
|
||||
void NotificationsManager::actionInvoked(uint, QString) {}
|
||||
|
|
|
|||
|
|
@ -16,231 +16,230 @@ CommunitiesModel::CommunitiesModel(QObject *parent)
|
|||
QHash<int, QByteArray>
|
||||
CommunitiesModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{AvatarUrl, "avatarUrl"},
|
||||
{DisplayName, "displayName"},
|
||||
{Tooltip, "tooltip"},
|
||||
{ChildrenHidden, "childrenHidden"},
|
||||
{Hidden, "hidden"},
|
||||
{Id, "id"},
|
||||
};
|
||||
return {
|
||||
{AvatarUrl, "avatarUrl"},
|
||||
{DisplayName, "displayName"},
|
||||
{Tooltip, "tooltip"},
|
||||
{ChildrenHidden, "childrenHidden"},
|
||||
{Hidden, "hidden"},
|
||||
{Id, "id"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
CommunitiesModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (index.row() == 0) {
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString(":/icons/icons/ui/world.png");
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
return tr("All rooms");
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return tr("Shows all rooms without filtering.");
|
||||
case CommunitiesModel::Roles::ChildrenHidden:
|
||||
return false;
|
||||
case CommunitiesModel::Roles::Hidden:
|
||||
return false;
|
||||
case CommunitiesModel::Roles::Id:
|
||||
return "";
|
||||
}
|
||||
} else if (index.row() - 1 < spaceOrder_.size()) {
|
||||
auto id = spaceOrder_.at(index.row() - 1);
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString::fromStdString(spaces_.at(id).avatar_url);
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return QString::fromStdString(spaces_.at(id).name);
|
||||
case CommunitiesModel::Roles::ChildrenHidden:
|
||||
return true;
|
||||
case CommunitiesModel::Roles::Hidden:
|
||||
return hiddentTagIds_.contains("space:" + id);
|
||||
case CommunitiesModel::Roles::Id:
|
||||
return "space:" + id;
|
||||
}
|
||||
} else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) {
|
||||
auto tag = tags_.at(index.row() - 1 - spaceOrder_.size());
|
||||
if (tag == "m.favourite") {
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString(":/icons/icons/ui/star.png");
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
return tr("Favourites");
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return tr("Rooms you have favourited.");
|
||||
}
|
||||
} else if (tag == "m.lowpriority") {
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString(":/icons/icons/ui/lowprio.png");
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
return tr("Low Priority");
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return tr("Rooms with low priority.");
|
||||
}
|
||||
} else if (tag == "m.server_notice") {
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString(":/icons/icons/ui/tag.png");
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
return tr("Server Notices");
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return tr("Messages from your server or administrator.");
|
||||
}
|
||||
} else {
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString(":/icons/icons/ui/tag.png");
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return tag.mid(2);
|
||||
}
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::Hidden:
|
||||
return hiddentTagIds_.contains("tag:" + tag);
|
||||
case CommunitiesModel::Roles::ChildrenHidden:
|
||||
return true;
|
||||
case CommunitiesModel::Roles::Id:
|
||||
return "tag:" + tag;
|
||||
}
|
||||
if (index.row() == 0) {
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString(":/icons/icons/ui/world.png");
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
return tr("All rooms");
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return tr("Shows all rooms without filtering.");
|
||||
case CommunitiesModel::Roles::ChildrenHidden:
|
||||
return false;
|
||||
case CommunitiesModel::Roles::Hidden:
|
||||
return false;
|
||||
case CommunitiesModel::Roles::Id:
|
||||
return "";
|
||||
}
|
||||
return QVariant();
|
||||
} else if (index.row() - 1 < spaceOrder_.size()) {
|
||||
auto id = spaceOrder_.at(index.row() - 1);
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString::fromStdString(spaces_.at(id).avatar_url);
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return QString::fromStdString(spaces_.at(id).name);
|
||||
case CommunitiesModel::Roles::ChildrenHidden:
|
||||
return true;
|
||||
case CommunitiesModel::Roles::Hidden:
|
||||
return hiddentTagIds_.contains("space:" + id);
|
||||
case CommunitiesModel::Roles::Id:
|
||||
return "space:" + id;
|
||||
}
|
||||
} else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) {
|
||||
auto tag = tags_.at(index.row() - 1 - spaceOrder_.size());
|
||||
if (tag == "m.favourite") {
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString(":/icons/icons/ui/star.png");
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
return tr("Favourites");
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return tr("Rooms you have favourited.");
|
||||
}
|
||||
} else if (tag == "m.lowpriority") {
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString(":/icons/icons/ui/lowprio.png");
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
return tr("Low Priority");
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return tr("Rooms with low priority.");
|
||||
}
|
||||
} else if (tag == "m.server_notice") {
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString(":/icons/icons/ui/tag.png");
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
return tr("Server Notices");
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return tr("Messages from your server or administrator.");
|
||||
}
|
||||
} else {
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::AvatarUrl:
|
||||
return QString(":/icons/icons/ui/tag.png");
|
||||
case CommunitiesModel::Roles::DisplayName:
|
||||
case CommunitiesModel::Roles::Tooltip:
|
||||
return tag.mid(2);
|
||||
}
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case CommunitiesModel::Roles::Hidden:
|
||||
return hiddentTagIds_.contains("tag:" + tag);
|
||||
case CommunitiesModel::Roles::ChildrenHidden:
|
||||
return true;
|
||||
case CommunitiesModel::Roles::Id:
|
||||
return "tag:" + tag;
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesModel::initializeSidebar()
|
||||
{
|
||||
beginResetModel();
|
||||
tags_.clear();
|
||||
spaceOrder_.clear();
|
||||
spaces_.clear();
|
||||
beginResetModel();
|
||||
tags_.clear();
|
||||
spaceOrder_.clear();
|
||||
spaces_.clear();
|
||||
|
||||
std::set<std::string> ts;
|
||||
std::vector<RoomInfo> tempSpaces;
|
||||
auto infos = cache::roomInfo();
|
||||
for (auto it = infos.begin(); it != infos.end(); it++) {
|
||||
if (it.value().is_space) {
|
||||
spaceOrder_.push_back(it.key());
|
||||
spaces_[it.key()] = it.value();
|
||||
} else {
|
||||
for (const auto &t : it.value().tags) {
|
||||
if (t.find("u.") == 0 || t.find("m." == 0)) {
|
||||
ts.insert(t);
|
||||
}
|
||||
}
|
||||
std::set<std::string> ts;
|
||||
std::vector<RoomInfo> tempSpaces;
|
||||
auto infos = cache::roomInfo();
|
||||
for (auto it = infos.begin(); it != infos.end(); it++) {
|
||||
if (it.value().is_space) {
|
||||
spaceOrder_.push_back(it.key());
|
||||
spaces_[it.key()] = it.value();
|
||||
} else {
|
||||
for (const auto &t : it.value().tags) {
|
||||
if (t.find("u.") == 0 || t.find("m." == 0)) {
|
||||
ts.insert(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &t : ts)
|
||||
tags_.push_back(QString::fromStdString(t));
|
||||
for (const auto &t : ts)
|
||||
tags_.push_back(QString::fromStdString(t));
|
||||
|
||||
hiddentTagIds_ = UserSettings::instance()->hiddenTags();
|
||||
endResetModel();
|
||||
hiddentTagIds_ = UserSettings::instance()->hiddenTags();
|
||||
endResetModel();
|
||||
|
||||
emit tagsChanged();
|
||||
emit hiddenTagsChanged();
|
||||
emit tagsChanged();
|
||||
emit hiddenTagsChanged();
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesModel::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
tags_.clear();
|
||||
endResetModel();
|
||||
resetCurrentTagId();
|
||||
beginResetModel();
|
||||
tags_.clear();
|
||||
endResetModel();
|
||||
resetCurrentTagId();
|
||||
|
||||
emit tagsChanged();
|
||||
emit tagsChanged();
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesModel::sync(const mtx::responses::Rooms &rooms)
|
||||
{
|
||||
bool tagsUpdated = false;
|
||||
bool tagsUpdated = false;
|
||||
|
||||
for (const auto &[roomid, room] : rooms.join) {
|
||||
(void)roomid;
|
||||
for (const auto &e : room.account_data.events)
|
||||
if (std::holds_alternative<
|
||||
mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
|
||||
tagsUpdated = true;
|
||||
}
|
||||
for (const auto &e : room.state.events)
|
||||
if (std::holds_alternative<
|
||||
mtx::events::StateEvent<mtx::events::state::space::Child>>(e) ||
|
||||
std::holds_alternative<
|
||||
mtx::events::StateEvent<mtx::events::state::space::Parent>>(e)) {
|
||||
tagsUpdated = true;
|
||||
}
|
||||
for (const auto &e : room.timeline.events)
|
||||
if (std::holds_alternative<
|
||||
mtx::events::StateEvent<mtx::events::state::space::Child>>(e) ||
|
||||
std::holds_alternative<
|
||||
mtx::events::StateEvent<mtx::events::state::space::Parent>>(e)) {
|
||||
tagsUpdated = true;
|
||||
}
|
||||
}
|
||||
for (const auto &[roomid, room] : rooms.leave) {
|
||||
(void)room;
|
||||
if (spaceOrder_.contains(QString::fromStdString(roomid)))
|
||||
tagsUpdated = true;
|
||||
}
|
||||
for (const auto &[roomid, room] : rooms.join) {
|
||||
(void)roomid;
|
||||
for (const auto &e : room.account_data.events)
|
||||
if (std::holds_alternative<
|
||||
mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
|
||||
tagsUpdated = true;
|
||||
}
|
||||
for (const auto &e : room.state.events)
|
||||
if (std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Child>>(
|
||||
e) ||
|
||||
std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Parent>>(
|
||||
e)) {
|
||||
tagsUpdated = true;
|
||||
}
|
||||
for (const auto &e : room.timeline.events)
|
||||
if (std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Child>>(
|
||||
e) ||
|
||||
std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Parent>>(
|
||||
e)) {
|
||||
tagsUpdated = true;
|
||||
}
|
||||
}
|
||||
for (const auto &[roomid, room] : rooms.leave) {
|
||||
(void)room;
|
||||
if (spaceOrder_.contains(QString::fromStdString(roomid)))
|
||||
tagsUpdated = true;
|
||||
}
|
||||
|
||||
if (tagsUpdated)
|
||||
initializeSidebar();
|
||||
if (tagsUpdated)
|
||||
initializeSidebar();
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesModel::setCurrentTagId(QString tagId)
|
||||
{
|
||||
if (tagId.startsWith("tag:")) {
|
||||
auto tag = tagId.mid(4);
|
||||
for (const auto &t : tags_) {
|
||||
if (t == tag) {
|
||||
this->currentTagId_ = tagId;
|
||||
emit currentTagIdChanged(currentTagId_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (tagId.startsWith("space:")) {
|
||||
auto tag = tagId.mid(6);
|
||||
for (const auto &t : spaceOrder_) {
|
||||
if (t == tag) {
|
||||
this->currentTagId_ = tagId;
|
||||
emit currentTagIdChanged(currentTagId_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (tagId.startsWith("tag:")) {
|
||||
auto tag = tagId.mid(4);
|
||||
for (const auto &t : tags_) {
|
||||
if (t == tag) {
|
||||
this->currentTagId_ = tagId;
|
||||
emit currentTagIdChanged(currentTagId_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (tagId.startsWith("space:")) {
|
||||
auto tag = tagId.mid(6);
|
||||
for (const auto &t : spaceOrder_) {
|
||||
if (t == tag) {
|
||||
this->currentTagId_ = tagId;
|
||||
emit currentTagIdChanged(currentTagId_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->currentTagId_ = "";
|
||||
emit currentTagIdChanged(currentTagId_);
|
||||
this->currentTagId_ = "";
|
||||
emit currentTagIdChanged(currentTagId_);
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesModel::toggleTagId(QString tagId)
|
||||
{
|
||||
if (hiddentTagIds_.contains(tagId)) {
|
||||
hiddentTagIds_.removeOne(tagId);
|
||||
UserSettings::instance()->setHiddenTags(hiddentTagIds_);
|
||||
} else {
|
||||
hiddentTagIds_.push_back(tagId);
|
||||
UserSettings::instance()->setHiddenTags(hiddentTagIds_);
|
||||
}
|
||||
if (hiddentTagIds_.contains(tagId)) {
|
||||
hiddentTagIds_.removeOne(tagId);
|
||||
UserSettings::instance()->setHiddenTags(hiddentTagIds_);
|
||||
} else {
|
||||
hiddentTagIds_.push_back(tagId);
|
||||
UserSettings::instance()->setHiddenTags(hiddentTagIds_);
|
||||
}
|
||||
|
||||
if (tagId.startsWith("tag:")) {
|
||||
auto idx = tags_.indexOf(tagId.mid(4));
|
||||
if (idx != -1)
|
||||
emit dataChanged(index(idx + 1 + spaceOrder_.size()),
|
||||
index(idx + 1 + spaceOrder_.size()),
|
||||
{Hidden});
|
||||
} else if (tagId.startsWith("space:")) {
|
||||
auto idx = spaceOrder_.indexOf(tagId.mid(6));
|
||||
if (idx != -1)
|
||||
emit dataChanged(index(idx + 1), index(idx + 1), {Hidden});
|
||||
}
|
||||
if (tagId.startsWith("tag:")) {
|
||||
auto idx = tags_.indexOf(tagId.mid(4));
|
||||
if (idx != -1)
|
||||
emit dataChanged(
|
||||
index(idx + 1 + spaceOrder_.size()), index(idx + 1 + spaceOrder_.size()), {Hidden});
|
||||
} else if (tagId.startsWith("space:")) {
|
||||
auto idx = spaceOrder_.indexOf(tagId.mid(6));
|
||||
if (idx != -1)
|
||||
emit dataChanged(index(idx + 1), index(idx + 1), {Hidden});
|
||||
}
|
||||
|
||||
emit hiddenTagsChanged();
|
||||
emit hiddenTagsChanged();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,64 +15,64 @@
|
|||
|
||||
class CommunitiesModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString currentTagId READ currentTagId WRITE setCurrentTagId NOTIFY
|
||||
currentTagIdChanged RESET resetCurrentTagId)
|
||||
Q_PROPERTY(QStringList tags READ tags NOTIFY tagsChanged)
|
||||
Q_PROPERTY(QStringList tagsWithDefault READ tagsWithDefault NOTIFY tagsChanged)
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString currentTagId READ currentTagId WRITE setCurrentTagId NOTIFY
|
||||
currentTagIdChanged RESET resetCurrentTagId)
|
||||
Q_PROPERTY(QStringList tags READ tags NOTIFY tagsChanged)
|
||||
Q_PROPERTY(QStringList tagsWithDefault READ tagsWithDefault NOTIFY tagsChanged)
|
||||
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
AvatarUrl = Qt::UserRole,
|
||||
DisplayName,
|
||||
Tooltip,
|
||||
ChildrenHidden,
|
||||
Hidden,
|
||||
Id,
|
||||
};
|
||||
enum Roles
|
||||
{
|
||||
AvatarUrl = Qt::UserRole,
|
||||
DisplayName,
|
||||
Tooltip,
|
||||
ChildrenHidden,
|
||||
Hidden,
|
||||
Id,
|
||||
};
|
||||
|
||||
CommunitiesModel(QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
(void)parent;
|
||||
return 1 + tags_.size() + spaceOrder_.size();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
CommunitiesModel(QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
(void)parent;
|
||||
return 1 + tags_.size() + spaceOrder_.size();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
public slots:
|
||||
void initializeSidebar();
|
||||
void sync(const mtx::responses::Rooms &rooms);
|
||||
void clear();
|
||||
QString currentTagId() const { return currentTagId_; }
|
||||
void setCurrentTagId(QString tagId);
|
||||
void resetCurrentTagId()
|
||||
{
|
||||
currentTagId_.clear();
|
||||
emit currentTagIdChanged(currentTagId_);
|
||||
}
|
||||
QStringList tags() const { return tags_; }
|
||||
QStringList tagsWithDefault() const
|
||||
{
|
||||
QStringList tagsWD = tags_;
|
||||
tagsWD.prepend("m.lowpriority");
|
||||
tagsWD.prepend("m.favourite");
|
||||
tagsWD.removeOne("m.server_notice");
|
||||
tagsWD.removeDuplicates();
|
||||
return tagsWD;
|
||||
}
|
||||
void toggleTagId(QString tagId);
|
||||
void initializeSidebar();
|
||||
void sync(const mtx::responses::Rooms &rooms);
|
||||
void clear();
|
||||
QString currentTagId() const { return currentTagId_; }
|
||||
void setCurrentTagId(QString tagId);
|
||||
void resetCurrentTagId()
|
||||
{
|
||||
currentTagId_.clear();
|
||||
emit currentTagIdChanged(currentTagId_);
|
||||
}
|
||||
QStringList tags() const { return tags_; }
|
||||
QStringList tagsWithDefault() const
|
||||
{
|
||||
QStringList tagsWD = tags_;
|
||||
tagsWD.prepend("m.lowpriority");
|
||||
tagsWD.prepend("m.favourite");
|
||||
tagsWD.removeOne("m.server_notice");
|
||||
tagsWD.removeDuplicates();
|
||||
return tagsWD;
|
||||
}
|
||||
void toggleTagId(QString tagId);
|
||||
|
||||
signals:
|
||||
void currentTagIdChanged(QString tagId);
|
||||
void hiddenTagsChanged();
|
||||
void tagsChanged();
|
||||
void currentTagIdChanged(QString tagId);
|
||||
void hiddenTagsChanged();
|
||||
void tagsChanged();
|
||||
|
||||
private:
|
||||
QStringList tags_;
|
||||
QString currentTagId_;
|
||||
QStringList hiddentTagIds_;
|
||||
QStringList spaceOrder_;
|
||||
std::map<QString, RoomInfo> spaces_;
|
||||
QStringList tags_;
|
||||
QString currentTagId_;
|
||||
QStringList hiddentTagIds_;
|
||||
QStringList spaceOrder_;
|
||||
std::map<QString, RoomInfo> spaces_;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,127 +13,126 @@
|
|||
QQmlComponent *
|
||||
DelegateChoice::delegate() const
|
||||
{
|
||||
return delegate_;
|
||||
return delegate_;
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChoice::setDelegate(QQmlComponent *delegate)
|
||||
{
|
||||
if (delegate != delegate_) {
|
||||
delegate_ = delegate;
|
||||
emit delegateChanged();
|
||||
emit changed();
|
||||
}
|
||||
if (delegate != delegate_) {
|
||||
delegate_ = delegate;
|
||||
emit delegateChanged();
|
||||
emit changed();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant
|
||||
DelegateChoice::roleValue() const
|
||||
{
|
||||
return roleValue_;
|
||||
return roleValue_;
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChoice::setRoleValue(const QVariant &value)
|
||||
{
|
||||
if (value != roleValue_) {
|
||||
roleValue_ = value;
|
||||
emit roleValueChanged();
|
||||
emit changed();
|
||||
}
|
||||
if (value != roleValue_) {
|
||||
roleValue_ = value;
|
||||
emit roleValueChanged();
|
||||
emit changed();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant
|
||||
DelegateChooser::roleValue() const
|
||||
{
|
||||
return roleValue_;
|
||||
return roleValue_;
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChooser::setRoleValue(const QVariant &value)
|
||||
{
|
||||
if (value != roleValue_) {
|
||||
roleValue_ = value;
|
||||
recalcChild();
|
||||
emit roleValueChanged();
|
||||
}
|
||||
if (value != roleValue_) {
|
||||
roleValue_ = value;
|
||||
recalcChild();
|
||||
emit roleValueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QQmlListProperty<DelegateChoice>
|
||||
DelegateChooser::choices()
|
||||
{
|
||||
return QQmlListProperty<DelegateChoice>(this,
|
||||
this,
|
||||
&DelegateChooser::appendChoice,
|
||||
&DelegateChooser::choiceCount,
|
||||
&DelegateChooser::choice,
|
||||
&DelegateChooser::clearChoices);
|
||||
return QQmlListProperty<DelegateChoice>(this,
|
||||
this,
|
||||
&DelegateChooser::appendChoice,
|
||||
&DelegateChooser::choiceCount,
|
||||
&DelegateChooser::choice,
|
||||
&DelegateChooser::clearChoices);
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChooser::appendChoice(QQmlListProperty<DelegateChoice> *p, DelegateChoice *c)
|
||||
{
|
||||
DelegateChooser *dc = static_cast<DelegateChooser *>(p->object);
|
||||
dc->choices_.append(c);
|
||||
DelegateChooser *dc = static_cast<DelegateChooser *>(p->object);
|
||||
dc->choices_.append(c);
|
||||
}
|
||||
|
||||
int
|
||||
DelegateChooser::choiceCount(QQmlListProperty<DelegateChoice> *p)
|
||||
{
|
||||
return static_cast<DelegateChooser *>(p->object)->choices_.count();
|
||||
return static_cast<DelegateChooser *>(p->object)->choices_.count();
|
||||
}
|
||||
DelegateChoice *
|
||||
DelegateChooser::choice(QQmlListProperty<DelegateChoice> *p, int index)
|
||||
{
|
||||
return static_cast<DelegateChooser *>(p->object)->choices_.at(index);
|
||||
return static_cast<DelegateChooser *>(p->object)->choices_.at(index);
|
||||
}
|
||||
void
|
||||
DelegateChooser::clearChoices(QQmlListProperty<DelegateChoice> *p)
|
||||
{
|
||||
static_cast<DelegateChooser *>(p->object)->choices_.clear();
|
||||
static_cast<DelegateChooser *>(p->object)->choices_.clear();
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChooser::recalcChild()
|
||||
{
|
||||
for (const auto choice : qAsConst(choices_)) {
|
||||
auto choiceValue = choice->roleValue();
|
||||
if (!roleValue_.isValid() || !choiceValue.isValid() || choiceValue == roleValue_) {
|
||||
if (child_) {
|
||||
child_->setParentItem(nullptr);
|
||||
child_ = nullptr;
|
||||
}
|
||||
for (const auto choice : qAsConst(choices_)) {
|
||||
auto choiceValue = choice->roleValue();
|
||||
if (!roleValue_.isValid() || !choiceValue.isValid() || choiceValue == roleValue_) {
|
||||
if (child_) {
|
||||
child_->setParentItem(nullptr);
|
||||
child_ = nullptr;
|
||||
}
|
||||
|
||||
choice->delegate()->create(incubator, QQmlEngine::contextForObject(this));
|
||||
return;
|
||||
}
|
||||
choice->delegate()->create(incubator, QQmlEngine::contextForObject(this));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChooser::componentComplete()
|
||||
{
|
||||
QQuickItem::componentComplete();
|
||||
recalcChild();
|
||||
QQuickItem::componentComplete();
|
||||
recalcChild();
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status status)
|
||||
{
|
||||
if (status == QQmlIncubator::Ready) {
|
||||
chooser.child_ = dynamic_cast<QQuickItem *>(object());
|
||||
if (chooser.child_ == nullptr) {
|
||||
nhlog::ui()->error("Delegate has to be derived of Item!");
|
||||
return;
|
||||
}
|
||||
|
||||
chooser.child_->setParentItem(&chooser);
|
||||
QQmlEngine::setObjectOwnership(chooser.child_,
|
||||
QQmlEngine::ObjectOwnership::JavaScriptOwnership);
|
||||
emit chooser.childChanged();
|
||||
|
||||
} else if (status == QQmlIncubator::Error) {
|
||||
for (const auto &e : errors())
|
||||
nhlog::ui()->error("Error instantiating delegate: {}",
|
||||
e.toString().toStdString());
|
||||
if (status == QQmlIncubator::Ready) {
|
||||
chooser.child_ = dynamic_cast<QQuickItem *>(object());
|
||||
if (chooser.child_ == nullptr) {
|
||||
nhlog::ui()->error("Delegate has to be derived of Item!");
|
||||
return;
|
||||
}
|
||||
|
||||
chooser.child_->setParentItem(&chooser);
|
||||
QQmlEngine::setObjectOwnership(chooser.child_,
|
||||
QQmlEngine::ObjectOwnership::JavaScriptOwnership);
|
||||
emit chooser.childChanged();
|
||||
|
||||
} else if (status == QQmlIncubator::Error) {
|
||||
for (const auto &e : errors())
|
||||
nhlog::ui()->error("Error instantiating delegate: {}", e.toString().toStdString());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,73 +18,73 @@ class QQmlAdaptorModel;
|
|||
|
||||
class DelegateChoice : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("DefaultProperty", "delegate")
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("DefaultProperty", "delegate")
|
||||
|
||||
public:
|
||||
Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
|
||||
Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
|
||||
Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
|
||||
Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
|
||||
|
||||
QQmlComponent *delegate() const;
|
||||
void setDelegate(QQmlComponent *delegate);
|
||||
QQmlComponent *delegate() const;
|
||||
void setDelegate(QQmlComponent *delegate);
|
||||
|
||||
QVariant roleValue() const;
|
||||
void setRoleValue(const QVariant &value);
|
||||
QVariant roleValue() const;
|
||||
void setRoleValue(const QVariant &value);
|
||||
|
||||
signals:
|
||||
void delegateChanged();
|
||||
void roleValueChanged();
|
||||
void changed();
|
||||
void delegateChanged();
|
||||
void roleValueChanged();
|
||||
void changed();
|
||||
|
||||
private:
|
||||
QVariant roleValue_;
|
||||
QQmlComponent *delegate_ = nullptr;
|
||||
QVariant roleValue_;
|
||||
QQmlComponent *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
class DelegateChooser : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("DefaultProperty", "choices")
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("DefaultProperty", "choices")
|
||||
|
||||
public:
|
||||
Q_PROPERTY(QQmlListProperty<DelegateChoice> choices READ choices CONSTANT)
|
||||
Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
|
||||
Q_PROPERTY(QQuickItem *child READ child NOTIFY childChanged)
|
||||
Q_PROPERTY(QQmlListProperty<DelegateChoice> choices READ choices CONSTANT)
|
||||
Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
|
||||
Q_PROPERTY(QQuickItem *child READ child NOTIFY childChanged)
|
||||
|
||||
QQmlListProperty<DelegateChoice> choices();
|
||||
QQmlListProperty<DelegateChoice> choices();
|
||||
|
||||
QVariant roleValue() const;
|
||||
void setRoleValue(const QVariant &value);
|
||||
QVariant roleValue() const;
|
||||
void setRoleValue(const QVariant &value);
|
||||
|
||||
QQuickItem *child() const { return child_; }
|
||||
QQuickItem *child() const { return child_; }
|
||||
|
||||
void recalcChild();
|
||||
void componentComplete() override;
|
||||
void recalcChild();
|
||||
void componentComplete() override;
|
||||
|
||||
signals:
|
||||
void roleChanged();
|
||||
void roleValueChanged();
|
||||
void childChanged();
|
||||
void roleChanged();
|
||||
void roleValueChanged();
|
||||
void childChanged();
|
||||
|
||||
private:
|
||||
struct DelegateIncubator : public QQmlIncubator
|
||||
{
|
||||
DelegateIncubator(DelegateChooser &parent)
|
||||
: QQmlIncubator(QQmlIncubator::AsynchronousIfNested)
|
||||
, chooser(parent)
|
||||
{}
|
||||
void statusChanged(QQmlIncubator::Status status) override;
|
||||
struct DelegateIncubator : public QQmlIncubator
|
||||
{
|
||||
DelegateIncubator(DelegateChooser &parent)
|
||||
: QQmlIncubator(QQmlIncubator::AsynchronousIfNested)
|
||||
, chooser(parent)
|
||||
{}
|
||||
void statusChanged(QQmlIncubator::Status status) override;
|
||||
|
||||
DelegateChooser &chooser;
|
||||
};
|
||||
DelegateChooser &chooser;
|
||||
};
|
||||
|
||||
QVariant roleValue_;
|
||||
QList<DelegateChoice *> choices_;
|
||||
QQuickItem *child_ = nullptr;
|
||||
DelegateIncubator incubator{*this};
|
||||
QVariant roleValue_;
|
||||
QList<DelegateChoice *> choices_;
|
||||
QQuickItem *child_ = nullptr;
|
||||
DelegateIncubator incubator{*this};
|
||||
|
||||
static void appendChoice(QQmlListProperty<DelegateChoice> *, DelegateChoice *);
|
||||
static int choiceCount(QQmlListProperty<DelegateChoice> *);
|
||||
static DelegateChoice *choice(QQmlListProperty<DelegateChoice> *, int index);
|
||||
static void clearChoices(QQmlListProperty<DelegateChoice> *);
|
||||
static void appendChoice(QQmlListProperty<DelegateChoice> *, DelegateChoice *);
|
||||
static int choiceCount(QQmlListProperty<DelegateChoice> *);
|
||||
static DelegateChoice *choice(QQmlListProperty<DelegateChoice> *, int index);
|
||||
static void clearChoices(QQmlListProperty<DelegateChoice> *);
|
||||
};
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue