Move all files under src/

This commit is contained in:
Konstantinos Sideris 2018-07-17 16:37:25 +03:00
parent 96a2c614bf
commit 0e814da91c
145 changed files with 281 additions and 279 deletions

View file

@ -20,7 +20,7 @@
#include "AvatarProvider.h"
#include "Cache.h"
#include "Logging.hpp"
#include "Logging.h"
#include "MatrixClient.h"
namespace AvatarProvider {

36
src/AvatarProvider.h Normal file
View file

@ -0,0 +1,36 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QImage>
#include <functional>
class AvatarProxy : public QObject
{
Q_OBJECT
signals:
void avatarDownloaded(const QByteArray &data);
};
using AvatarCallback = std::function<void(QImage)>;
namespace AvatarProvider {
void
resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback cb);
}

View file

@ -28,7 +28,6 @@
#include <variant.hpp>
#include "Cache.h"
#include "Logging.hpp"
#include "Utils.h"
//! Should be changed when a breaking change occurs in the cache format.

661
src/Cache.h Normal file
View file

@ -0,0 +1,661 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <boost/optional.hpp>
#include <QDateTime>
#include <QDir>
#include <QImage>
#include <QString>
#include <json.hpp>
#include <lmdb++.h>
#include <mtx/events/join_rules.hpp>
#include <mtx/responses.hpp>
#include <mtxclient/crypto/client.hpp>
#include <mutex>
#include "Logging.h"
using mtx::events::state::JoinRule;
struct RoomMember
{
QString user_id;
QString display_name;
QImage avatar;
};
struct SearchResult
{
QString user_id;
QString display_name;
};
static int
numeric_key_comparison(const MDB_val *a, const MDB_val *b)
{
auto lhs = std::stoull(std::string((char *)a->mv_data, a->mv_size));
auto rhs = std::stoull(std::string((char *)b->mv_data, b->mv_size));
if (lhs < rhs)
return 1;
else if (lhs == rhs)
return 0;
return -1;
}
Q_DECLARE_METATYPE(SearchResult)
Q_DECLARE_METATYPE(QVector<SearchResult>)
Q_DECLARE_METATYPE(RoomMember)
Q_DECLARE_METATYPE(mtx::responses::Timeline)
//! Used to uniquely identify a list of read receipts.
struct ReadReceiptKey
{
std::string event_id;
std::string room_id;
};
inline void
to_json(json &j, const ReadReceiptKey &key)
{
j = json{{"event_id", key.event_id}, {"room_id", key.room_id}};
}
inline void
from_json(const json &j, ReadReceiptKey &key)
{
key.event_id = j.at("event_id").get<std::string>();
key.room_id = j.at("room_id").get<std::string>();
}
struct DescInfo
{
QString username;
QString userid;
QString body;
QString timestamp;
QDateTime datetime;
};
//! 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;
//! Whether or not the room is an invite.
bool is_invite = false;
//! Total number of members in the room.
int16_t member_count = 0;
//! Who can access to the room.
JoinRule join_rule = JoinRule::Public;
bool guest_access = false;
//! Metadata describing the last message in the timeline.
DescInfo msgInfo;
};
inline void
to_json(json &j, const RoomInfo &info)
{
j["name"] = info.name;
j["topic"] = info.topic;
j["avatar_url"] = info.avatar_url;
j["is_invite"] = info.is_invite;
j["join_rule"] = info.join_rule;
j["guest_access"] = info.guest_access;
if (info.member_count != 0)
j["member_count"] = info.member_count;
}
inline void
from_json(const json &j, RoomInfo &info)
{
info.name = j.at("name");
info.topic = j.at("topic");
info.avatar_url = j.at("avatar_url");
info.is_invite = j.at("is_invite");
info.join_rule = j.at("join_rule");
info.guest_access = j.at("guest_access");
if (j.count("member_count"))
info.member_count = j.at("member_count");
}
//! Basic information per member;
struct MemberInfo
{
std::string name;
std::string avatar_url;
};
inline void
to_json(json &j, const MemberInfo &info)
{
j["name"] = info.name;
j["avatar_url"] = info.avatar_url;
}
inline void
from_json(const json &j, MemberInfo &info)
{
info.name = j.at("name");
info.avatar_url = j.at("avatar_url");
}
struct RoomSearchResult
{
std::string room_id;
RoomInfo info;
QImage img;
};
Q_DECLARE_METATYPE(RoomSearchResult)
Q_DECLARE_METATYPE(RoomInfo)
// Extra information associated with an outbound megolm session.
struct OutboundGroupSessionData
{
std::string session_id;
std::string session_key;
uint64_t message_index = 0;
};
inline void
to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg)
{
obj["session_id"] = msg.session_id;
obj["session_key"] = msg.session_key;
obj["message_index"] = msg.message_index;
}
inline void
from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg)
{
msg.session_id = obj.at("session_id");
msg.session_key = obj.at("session_key");
msg.message_index = obj.at("message_index");
}
struct OutboundGroupSessionDataRef
{
OlmOutboundGroupSession *session;
OutboundGroupSessionData data;
};
struct DevicePublicKeys
{
std::string ed25519;
std::string curve25519;
};
inline void
to_json(nlohmann::json &obj, const DevicePublicKeys &msg)
{
obj["ed25519"] = msg.ed25519;
obj["curve25519"] = msg.curve25519;
}
inline void
from_json(const nlohmann::json &obj, DevicePublicKeys &msg)
{
msg.ed25519 = obj.at("ed25519");
msg.curve25519 = obj.at("curve25519");
}
//! Represents a unique megolm session identifier.
struct MegolmSessionIndex
{
//! 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;
//! Representation to be used in a hash map.
std::string to_hash() const { return room_id + session_id + sender_key; }
};
struct OlmSessionStorage
{
// Megolm sessions
std::map<std::string, mtx::crypto::InboundGroupSessionPtr> group_inbound_sessions;
std::map<std::string, mtx::crypto::OutboundGroupSessionPtr> group_outbound_sessions;
std::map<std::string, OutboundGroupSessionData> group_outbound_session_data;
// Guards for accessing megolm sessions.
std::mutex group_outbound_mtx;
std::mutex group_inbound_mtx;
};
class Cache : public QObject
{
Q_OBJECT
public:
Cache(const QString &userId, QObject *parent = nullptr);
static QHash<QString, QString> DisplayNames;
static QHash<QString, QString> AvatarUrls;
static std::string displayName(const std::string &room_id, const std::string &user_id);
static QString displayName(const QString &room_id, const QString &user_id);
static QString avatarUrl(const QString &room_id, const QString &user_id);
static void removeDisplayName(const QString &room_id, const QString &user_id);
static void removeAvatarUrl(const QString &room_id, const QString &user_id);
static void insertDisplayName(const QString &room_id,
const QString &user_id,
const QString &display_name);
static void insertAvatarUrl(const QString &room_id,
const QString &user_id,
const QString &avatar_url);
//! Load saved data for the display names & avatars.
void populateMembers();
std::vector<std::string> joinedRooms();
QMap<QString, RoomInfo> roomInfo(bool withInvites = true);
std::map<QString, bool> invites();
//! Calculate & return the name of the room.
QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
//! Get room join rules
JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb);
bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb);
//! Retrieve the topic of the room if any.
QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
//! Retrieve the room avatar's url if any.
QString getRoomAvatarUrl(lmdb::txn &txn,
lmdb::dbi &statesdb,
lmdb::dbi &membersdb,
const QString &room_id);
//! Retrieve member info from a room.
std::vector<RoomMember> getMembers(const std::string &room_id,
std::size_t startIndex = 0,
std::size_t len = 30);
void saveState(const mtx::responses::Sync &res);
bool isInitialized() const;
std::string nextBatchToken() const;
void deleteData();
void removeInvite(lmdb::txn &txn, const std::string &room_id);
void removeInvite(const std::string &room_id);
void removeRoom(lmdb::txn &txn, const std::string &roomid);
void removeRoom(const std::string &roomid);
void removeRoom(const QString &roomid) { removeRoom(roomid.toStdString()); };
void setup();
bool isFormatValid();
void setCurrentFormat();
std::map<QString, mtx::responses::Timeline> roomMessages();
//! Retrieve all the user ids from a room.
std::vector<std::string> roomMembers(const std::string &room_id);
//! Check if the given user has power leve greater than than
//! lowest power level of the given events.
bool hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
const std::string &room_id,
const std::string &user_id);
//! Retrieves the saved room avatar.
QImage getRoomAvatar(const QString &id);
QImage getRoomAvatar(const std::string &id);
//! Adds a user to the read list for the given event.
//!
//! There should be only one user id present in a receipt list per room.
//! The user id should be removed from any other lists.
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
void updateReadReceipt(lmdb::txn &txn,
const std::string &room_id,
const Receipts &receipts);
//! Retrieve all the read receipts for the given event id and room.
//!
//! Returns a map of user ids and the time of the read receipt in milliseconds.
using UserReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
UserReceipts readReceipts(const QString &event_id, const QString &room_id);
QByteArray image(const QString &url) const;
QByteArray image(lmdb::txn &txn, const std::string &url) const;
QByteArray image(const std::string &url) const
{
return image(QString::fromStdString(url));
}
void saveImage(const std::string &url, const std::string &data);
void saveImage(const QString &url, const QByteArray &data);
RoomInfo singleRoomInfo(const std::string &room_id);
std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
std::map<QString, RoomInfo> getRoomInfo(const std::vector<std::string> &rooms);
std::map<QString, RoomInfo> roomUpdates(const mtx::responses::Sync &sync)
{
return getRoomInfo(roomsWithStateUpdates(sync));
}
QVector<SearchResult> searchUsers(const std::string &room_id,
const std::string &query,
std::uint8_t max_items = 5);
std::vector<RoomSearchResult> searchRooms(const std::string &query,
std::uint8_t max_items = 5);
void markSentNotification(const std::string &event_id);
//! Removes an event from the sent notifications.
void removeReadNotification(const std::string &event_id);
//! Check if we have sent a desktop notification for the given event id.
bool isNotificationSent(const std::string &event_id);
//! Mark a room that uses e2e encryption.
void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
bool isRoomEncrypted(const std::string &room_id);
//! Save the public keys for a device.
void saveDeviceKeys(const std::string &device_id);
void getDeviceKeys(const std::string &device_id);
//! Save the device list for a user.
void setDeviceList(const std::string &user_id, const std::vector<std::string> &devices);
std::vector<std::string> getDeviceList(const std::string &user_id);
//
// Outbound Megolm Sessions
//
void saveOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data,
mtx::crypto::OutboundGroupSessionPtr session);
OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
void updateOutboundMegolmSession(const std::string &room_id, int message_index);
//
// Inbound Megolm Sessions
//
void saveInboundMegolmSession(const MegolmSessionIndex &index,
mtx::crypto::InboundGroupSessionPtr session);
OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
bool inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept;
//
// Olm Sessions
//
void saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
std::vector<std::string> getOlmSessions(const std::string &curve25519);
boost::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519,
const std::string &session_id);
void saveOlmAccount(const std::string &pickled);
std::string restoreOlmAccount();
void restoreSessions();
OlmSessionStorage session_storage;
private:
//! Save an invited room.
void saveInvite(lmdb::txn &txn,
lmdb::dbi &statesdb,
lmdb::dbi &membersdb,
const mtx::responses::InvitedRoom &room);
QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
DescInfo getLastMessageInfo(lmdb::txn &txn, const std::string &room_id);
void saveTimelineMessages(lmdb::txn &txn,
const std::string &room_id,
const mtx::responses::Timeline &res);
mtx::responses::Timeline getTimelineMessages(lmdb::txn &txn, const std::string &room_id);
//! Remove a room from the cache.
// void removeLeftRoom(lmdb::txn &txn, const std::string &room_id);
template<class T>
void saveStateEvents(lmdb::txn &txn,
const lmdb::dbi &statesdb,
const lmdb::dbi &membersdb,
const std::string &room_id,
const std::vector<T> &events)
{
for (const auto &e : events)
saveStateEvent(txn, statesdb, membersdb, room_id, e);
}
template<class T>
void saveStateEvent(lmdb::txn &txn,
const lmdb::dbi &statesdb,
const lmdb::dbi &membersdb,
const std::string &room_id,
const T &event)
{
using namespace mtx::events;
using namespace mtx::events::state;
if (mpark::holds_alternative<StateEvent<Member>>(event)) {
const auto e = mpark::get<StateEvent<Member>>(event);
switch (e.content.membership) {
//
// We only keep users with invite or join membership.
//
case Membership::Invite:
case Membership::Join: {
auto display_name = e.content.display_name.empty()
? e.state_key
: e.content.display_name;
// Lightweight representation of a member.
MemberInfo tmp{display_name, e.content.avatar_url};
lmdb::dbi_put(txn,
membersdb,
lmdb::val(e.state_key),
lmdb::val(json(tmp).dump()));
insertDisplayName(QString::fromStdString(room_id),
QString::fromStdString(e.state_key),
QString::fromStdString(display_name));
insertAvatarUrl(QString::fromStdString(room_id),
QString::fromStdString(e.state_key),
QString::fromStdString(e.content.avatar_url));
break;
}
default: {
lmdb::dbi_del(
txn, membersdb, lmdb::val(e.state_key), lmdb::val(""));
removeDisplayName(QString::fromStdString(room_id),
QString::fromStdString(e.state_key));
removeAvatarUrl(QString::fromStdString(room_id),
QString::fromStdString(e.state_key));
break;
}
}
return;
} else if (mpark::holds_alternative<StateEvent<Encryption>>(event)) {
setEncryptedRoom(txn, room_id);
return;
}
if (!isStateEvent(event))
return;
mpark::visit(
[&txn, &statesdb](auto e) {
lmdb::dbi_put(
txn, statesdb, lmdb::val(to_string(e.type)), lmdb::val(json(e).dump()));
},
event);
}
template<class T>
bool isStateEvent(const T &e)
{
using namespace mtx::events;
using namespace mtx::events::state;
return mpark::holds_alternative<StateEvent<Aliases>>(e) ||
mpark::holds_alternative<StateEvent<state::Avatar>>(e) ||
mpark::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
mpark::holds_alternative<StateEvent<Create>>(e) ||
mpark::holds_alternative<StateEvent<GuestAccess>>(e) ||
mpark::holds_alternative<StateEvent<HistoryVisibility>>(e) ||
mpark::holds_alternative<StateEvent<JoinRules>>(e) ||
mpark::holds_alternative<StateEvent<Name>>(e) ||
mpark::holds_alternative<StateEvent<Member>>(e) ||
mpark::holds_alternative<StateEvent<PowerLevels>>(e) ||
mpark::holds_alternative<StateEvent<Topic>>(e);
}
template<class T>
bool containsStateUpdates(const T &e)
{
using namespace mtx::events;
using namespace mtx::events::state;
return mpark::holds_alternative<StateEvent<state::Avatar>>(e) ||
mpark::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
mpark::holds_alternative<StateEvent<Name>>(e) ||
mpark::holds_alternative<StateEvent<Member>>(e) ||
mpark::holds_alternative<StateEvent<Topic>>(e);
}
bool containsStateUpdates(const mtx::events::collections::StrippedEvents &e)
{
using namespace mtx::events;
using namespace mtx::events::state;
return mpark::holds_alternative<StrippedEvent<state::Avatar>>(e) ||
mpark::holds_alternative<StrippedEvent<CanonicalAlias>>(e) ||
mpark::holds_alternative<StrippedEvent<Name>>(e) ||
mpark::holds_alternative<StrippedEvent<Member>>(e) ||
mpark::holds_alternative<StrippedEvent<Topic>>(e);
}
void saveInvites(lmdb::txn &txn,
const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
//! Sends signals for the rooms that are removed.
void removeLeftRooms(lmdb::txn &txn,
const std::map<std::string, mtx::responses::LeftRoom> &rooms)
{
for (const auto &room : rooms) {
removeRoom(txn, room.first);
// Clean up leftover invites.
removeInvite(txn, room.first);
}
}
lmdb::dbi getMessagesDb(lmdb::txn &txn, const std::string &room_id)
{
auto db =
lmdb::dbi::open(txn, std::string(room_id + "/messages").c_str(), MDB_CREATE);
lmdb::dbi_set_compare(txn, db, numeric_key_comparison);
return db;
}
lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE);
}
lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE);
}
lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE);
}
lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
}
//! Retrieves or creates the database that stores the open OLM sessions between our device
//! and the given curve25519 key which represents another device.
//!
//! Each entry is a map from the session_id to the pickled representation of the session.
lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key)
{
return lmdb::dbi::open(
txn, std::string("olm_sessions/" + curve25519_key).c_str(), MDB_CREATE);
}
QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event)
{
if (!event.content.display_name.empty())
return QString::fromStdString(event.content.display_name);
return QString::fromStdString(event.state_key);
}
void setNextBatchToken(lmdb::txn &txn, const std::string &token);
void setNextBatchToken(lmdb::txn &txn, const QString &token);
lmdb::env env_;
lmdb::dbi syncStateDb_;
lmdb::dbi roomsDb_;
lmdb::dbi invitesDb_;
lmdb::dbi mediaDb_;
lmdb::dbi readReceiptsDb_;
lmdb::dbi notificationsDb_;
lmdb::dbi devicesDb_;
lmdb::dbi deviceKeysDb_;
lmdb::dbi inboundMegolmSessionDb_;
lmdb::dbi outboundMegolmSessionDb_;
QString localUserId_;
QString cacheDirectory_;
};
namespace cache {
void
init(const QString &user_id);
Cache *
client();
}

View file

@ -23,22 +23,22 @@
#include "AvatarProvider.h"
#include "Cache.h"
#include "ChatPage.h"
#include "Logging.hpp"
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "Olm.hpp"
#include "OverlayModal.h"
#include "Olm.h"
#include "QuickSwitcher.h"
#include "RoomList.h"
#include "SideBarActions.h"
#include "Splitter.h"
#include "TextInputWidget.h"
#include "Theme.h"
#include "TopRoomBar.h"
#include "TypingDisplay.h"
#include "UserInfoWidget.h"
#include "UserSettingsPage.h"
#include "Utils.h"
#include "ui/OverlayModal.h"
#include "ui/Theme.h"
#include "notifications/Manager.h"

268
src/ChatPage.h Normal file
View file

@ -0,0 +1,268 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <atomic>
#include <QFrame>
#include <QHBoxLayout>
#include <QMap>
#include <QPixmap>
#include <QTimer>
#include <QWidget>
#include "Cache.h"
#include "CommunitiesList.h"
#include "MatrixClient.h"
#include "notifications/Manager.h"
class OverlayModal;
class QuickSwitcher;
class RoomList;
class SideBarActions;
class Splitter;
class TextInputWidget;
class TimelineViewManager;
class TopRoomBar;
class TypingDisplay;
class UserInfoWidget;
class UserSettings;
class NotificationsManager;
namespace dialogs {
class ReadReceipts;
}
constexpr int CONSENSUS_TIMEOUT = 1000;
constexpr int SHOW_CONTENT_TIMEOUT = 3000;
constexpr int TYPING_REFRESH_TIMEOUT = 10000;
class ChatPage : public QWidget
{
Q_OBJECT
public:
ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent = 0);
// Initialize all the components of the UI.
void bootstrap(QString userid, QString homeserver, QString token);
void showQuickSwitcher();
void showReadReceipts(const QString &event_id);
QString currentRoom() const { return current_room_; }
static ChatPage *instance() { return instance_; }
QSharedPointer<UserSettings> userSettings() { return userSettings_; }
void deleteConfigs();
//! Calculate the width of the message timeline.
int timelineWidth();
bool isSideBarExpanded();
//! Hide the room & group list (if it was visible).
void hideSideBars();
//! Show the room/group list (if it was visible).
void showSideBars();
public slots:
void leaveRoom(const QString &room_id);
signals:
void connectionLost();
void connectionRestored();
void messageReply(const QString &username, const QString &msg);
void notificationsRetrieved(const mtx::responses::Notifications &);
void uploadFailed(const QString &msg);
void imageUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
qint64 dsize,
const QSize &dimensions);
void fileUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
qint64 dsize);
void audioUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
qint64 dsize);
void videoUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
qint64 dsize);
void contentLoaded();
void closing();
void changeWindowTitle(const QString &msg);
void unreadMessages(int count);
void showNotification(const QString &msg);
void showLoginPage(const QString &msg);
void showUserSettingsPage();
void showOverlayProgressBar();
void removeTimelineEvent(const QString &room_id, const QString &event_id);
void ownProfileOk();
void setUserDisplayName(const QString &name);
void setUserAvatar(const QImage &avatar);
void loggedOut();
void trySyncCb();
void tryDelayedSyncCb();
void tryInitialSyncCb();
void leftRoom(const QString &room_id);
void initializeRoomList(QMap<QString, RoomInfo>);
void initializeViews(const mtx::responses::Rooms &rooms);
void initializeEmptyViews(const std::map<QString, mtx::responses::Timeline> &msgs);
void syncUI(const mtx::responses::Rooms &rooms);
void syncRoomlist(const std::map<QString, RoomInfo> &updates);
void syncTopBar(const std::map<QString, RoomInfo> &updates);
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 updateGroupsInfo(const mtx::responses::JoinedGroups &groups);
private slots:
void showUnreadMessageNotification(int count);
void updateTopBarAvatar(const QString &roomid, const QPixmap &img);
void changeTopRoomInfo(const QString &room_id);
void logout();
void removeRoom(const QString &room_id);
void dropToLoginPage(const QString &msg);
void joinRoom(const QString &room);
void createRoom(const mtx::requests::CreateRoom &req);
void sendTypingNotifications();
private:
static ChatPage *instance_;
//! Handler callback for initial sync. It doesn't run on the main thread so all
//! communication with the GUI should be done through signals.
void initialSyncHandler(const mtx::responses::Sync &res, mtx::http::RequestErr err);
void tryInitialSync();
void trySync();
void ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts);
void getProfileInfo();
//! Check if the given room is currently open.
bool isRoomActive(const QString &room_id)
{
return isActiveWindow() && currentRoom() == room_id;
}
using UserID = QString;
using Membership = mtx::events::StateEvent<mtx::events::state::Member>;
using Memberships = std::map<std::string, Membership>;
using LeftRooms = std::map<std::string, mtx::responses::LeftRoom>;
void removeLeftRooms(const LeftRooms &rooms);
void updateTypingUsers(const QString &roomid, const std::vector<std::string> &user_ids);
void loadStateFromCache();
void resetUI();
//! Decides whether or not to hide the group's sidebar.
void setGroupViewState(bool isEnabled);
template<class Collection>
Memberships getMemberships(const std::vector<Collection> &events) const;
//! Update the room with the new notification count.
void updateRoomNotificationCount(const QString &room_id, uint16_t notification_count);
//! Send desktop notification for the received messages.
void sendDesktopNotifications(const mtx::responses::Notifications &);
QStringList generateTypingUsers(const QString &room_id,
const std::vector<std::string> &typing_users);
QHBoxLayout *topLayout_;
Splitter *splitter;
QWidget *sideBar_;
QVBoxLayout *sideBarLayout_;
QWidget *sideBarTopWidget_;
QVBoxLayout *sideBarTopWidgetLayout_;
QFrame *content_;
QVBoxLayout *contentLayout_;
CommunitiesList *communitiesList_;
RoomList *room_list_;
TimelineViewManager *view_manager_;
SideBarActions *sidebarActions_;
TopRoomBar *top_bar_;
TextInputWidget *text_input_;
TypingDisplay *typingDisplay_;
QTimer connectivityTimer_;
std::atomic_bool isConnected_;
QString current_room_;
QString current_community_;
UserInfoWidget *user_info_widget_;
// Keeps track of the users currently typing on each room.
std::map<QString, QList<QString>> typingUsers_;
QTimer *typingRefresher_;
QSharedPointer<QuickSwitcher> quickSwitcher_;
QSharedPointer<OverlayModal> quickSwitcherModal_;
QSharedPointer<dialogs::ReadReceipts> receiptsDialog_;
QSharedPointer<OverlayModal> receiptsModal_;
// Global user settings.
QSharedPointer<UserSettings> userSettings_;
NotificationsManager notificationsManager;
};
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;
using Member = mtx::events::StateEvent<mtx::events::state::Member>;
for (const auto &event : collection) {
if (mpark::holds_alternative<Member>(event)) {
auto member = mpark::get<Member>(event);
memberships.emplace(member.state_key, member);
}
}
return memberships;
}

View file

@ -1,6 +1,6 @@
#include "CommunitiesList.h"
#include "Cache.h"
#include "Logging.hpp"
#include "Logging.h"
#include "MatrixClient.h"
#include <QLabel>

50
src/CommunitiesList.h Normal file
View file

@ -0,0 +1,50 @@
#pragma once
#include <QScrollArea>
#include <QSharedPointer>
#include <QVBoxLayout>
#include "CommunitiesListItem.h"
#include "ui/Theme.h"
class CommunitiesList : public QWidget
{
Q_OBJECT
public:
CommunitiesList(QWidget *parent = nullptr);
void clear() { communities_.clear(); }
void addCommunity(const std::string &id);
void removeCommunity(const QString &id) { communities_.erase(id); };
std::vector<QString> roomList(const QString &id) const;
signals:
void communityChanged(const QString &id);
void avatarRetrieved(const QString &id, const QPixmap &img);
void groupProfileRetrieved(const QString &group_id, const mtx::responses::GroupProfile &);
void groupRoomsRetrieved(const QString &group_id, const std::vector<QString> &res);
public slots:
void updateCommunityAvatar(const QString &id, const QPixmap &img);
void highlightSelectedCommunity(const QString &id);
void setCommunities(const mtx::responses::JoinedGroups &groups);
private:
void fetchCommunityAvatar(const QString &id, const QString &avatarUrl);
void addGlobalItem() { addCommunity("world"); }
//! Check whether or not a community id is currently managed.
bool communityExists(const QString &id) const
{
return communities_.find(id) != communities_.end();
}
QVBoxLayout *topLayout_;
QVBoxLayout *contentsLayout_;
QWidget *scrollAreaContents_;
QScrollArea *scrollArea_;
std::map<QString, QSharedPointer<CommunitiesListItem>> communities_;
};

View file

@ -1,8 +1,8 @@
#include "CommunitiesListItem.h"
#include "Painter.h"
#include "Ripple.h"
#include "RippleOverlay.h"
#include "Utils.h"
#include "ui/Painter.h"
#include "ui/Ripple.h"
#include "ui/RippleOverlay.h"
CommunitiesListItem::CommunitiesListItem(QString group_id, QWidget *parent)
: QWidget(parent)

88
src/CommunitiesListItem.h Normal file
View file

@ -0,0 +1,88 @@
#pragma once
#include <QDebug>
#include <QMouseEvent>
#include <QPainter>
#include <QSharedPointer>
#include <QWidget>
#include <mtx/responses/groups.hpp>
#include "Config.h"
#include "ui/Theme.h"
class RippleOverlay;
class CommunitiesListItem : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor highlightedBackgroundColor READ highlightedBackgroundColor WRITE
setHighlightedBackgroundColor)
Q_PROPERTY(
QColor hoverBackgroundColor READ hoverBackgroundColor WRITE setHoverBackgroundColor)
Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
Q_PROPERTY(QColor avatarFgColor READ avatarFgColor WRITE setAvatarFgColor)
Q_PROPERTY(QColor avatarBgColor READ avatarBgColor WRITE setAvatarBgColor)
public:
CommunitiesListItem(QString group_id, QWidget *parent = nullptr);
void setName(QString name) { name_ = name; }
bool isPressed() const { return isPressed_; }
void setAvatar(const QImage &img);
void setRooms(std::vector<QString> room_ids) { room_ids_ = std::move(room_ids); }
std::vector<QString> rooms() const { return room_ids_; }
QColor highlightedBackgroundColor() const { return highlightedBackgroundColor_; }
QColor hoverBackgroundColor() const { return hoverBackgroundColor_; }
QColor backgroundColor() const { return backgroundColor_; }
QColor avatarFgColor() const { return avatarFgColor_; }
QColor avatarBgColor() const { return avatarBgColor_; }
void setHighlightedBackgroundColor(QColor &color) { highlightedBackgroundColor_ = color; }
void setHoverBackgroundColor(QColor &color) { hoverBackgroundColor_ = color; }
void setBackgroundColor(QColor &color) { backgroundColor_ = color; }
void setAvatarFgColor(QColor &color) { avatarFgColor_ = color; }
void setAvatarBgColor(QColor &color) { avatarBgColor_ = color; }
QSize sizeHint() const override
{
return QSize(IconSize + IconSize / 3, IconSize + IconSize / 3);
}
signals:
void clicked(const QString &group_id);
public slots:
void setPressedState(bool state);
protected:
void mousePressEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private:
const int IconSize = 36;
QString resolveName() const;
std::vector<QString> room_ids_;
QString name_;
QString groupId_;
QPixmap avatar_;
QColor highlightedBackgroundColor_;
QColor hoverBackgroundColor_;
QColor backgroundColor_;
QColor avatarFgColor_;
QColor avatarBgColor_;
bool isPressed_ = false;
RippleOverlay *rippleOverlay_;
};

106
src/Config.h Normal file
View file

@ -0,0 +1,106 @@
#pragma once
#include <QRegExp>
#include <QString>
// Non-theme app configuration. Layouts, fonts spacing etc.
//
// Font sizes are in pixels.
namespace conf {
constexpr int sideBarCollapsePoint = 450;
// Global settings.
constexpr int fontSize = 14;
constexpr int textInputFontSize = 14;
constexpr int emojiSize = 14;
constexpr int headerFontSize = 21;
constexpr int typingNotificationFontSize = 11;
namespace popup {
constexpr int font = fontSize;
constexpr int avatar = 28;
}
namespace modals {
constexpr int errorFont = conf::fontSize - 2;
}
namespace receipts {
constexpr int font = 12;
}
namespace dialogs {
constexpr int labelSize = 15;
}
namespace strings {
const QString url_html = "<a href=\"\\1\">\\1</a>";
const QRegExp url_regex(
"((www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]\\)\\:])");
}
// Window geometry.
namespace window {
constexpr int height = 600;
constexpr int width = 1066;
constexpr int minHeight = height;
constexpr int minWidth = 950;
} // namespace window
namespace textInput {
constexpr int height = 50;
}
namespace sidebarActions {
constexpr int height = textInput::height;
constexpr int iconSize = 28;
}
// Button settings.
namespace btn {
constexpr int fontSize = 20;
constexpr int cornerRadius = 3;
} // namespace btn
// RoomList specific.
namespace roomlist {
namespace fonts {
constexpr int heading = 13;
constexpr int timestamp = heading;
constexpr int badge = 10;
constexpr int bubble = 20;
constexpr int communityBubble = bubble - 4;
} // namespace fonts
} // namespace roomlist
namespace userInfoWidget {
namespace fonts {
constexpr int displayName = 15;
constexpr int userid = 13;
} // namespace fonts
} // namespace userInfoWidget
namespace topRoomBar {
namespace fonts {
constexpr int roomName = 15;
constexpr int roomDescription = 14;
} // namespace fonts
} // namespace topRoomBar
namespace timeline {
constexpr int msgAvatarTopMargin = 15;
constexpr int msgTopMargin = 2;
constexpr int msgLeftMargin = 14;
constexpr int avatarSize = 36;
constexpr int headerSpacing = 3;
constexpr int headerLeftMargin = 15;
namespace fonts {
constexpr int timestamp = 13;
constexpr int indicator = timestamp - 2;
constexpr int dateSeparator = conf::fontSize;
} // namespace fonts
} // namespace timeline
} // namespace conf

View file

@ -1,8 +1,8 @@
#include <QHBoxLayout>
#include "FlatButton.h"
#include "InviteeItem.h"
#include "Theme.h"
#include "ui/FlatButton.h"
#include "ui/Theme.h"
constexpr int SidePadding = 10;
constexpr int IconSize = 13;

27
src/InviteeItem.h Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include <QLabel>
#include <QWidget>
#include "mtx.hpp"
class FlatButton;
class InviteeItem : public QWidget
{
Q_OBJECT
public:
InviteeItem(mtx::identifiers::User user, QWidget *parent = nullptr);
QString userID() { return user_; }
signals:
void removeItem();
private:
QString user_;
QLabel *name_;
FlatButton *removeUserBtn_;
};

View file

@ -1,4 +1,4 @@
#include "Logging.hpp"
#include "Logging.h"
#include <iostream>
#include <spdlog/sinks/file_sinks.h>

21
src/Logging.h Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include <memory>
#include <spdlog/spdlog.h>
namespace nhlog {
void
init(const std::string &file);
std::shared_ptr<spdlog::logger>
ui();
std::shared_ptr<spdlog::logger>
net();
std::shared_ptr<spdlog::logger>
db();
std::shared_ptr<spdlog::logger>
crypto();
}

View file

@ -20,13 +20,13 @@
#include <mtx/identifiers.hpp>
#include "Config.h"
#include "FlatButton.h"
#include "LoadingIndicator.h"
#include "LoginPage.h"
#include "MatrixClient.h"
#include "OverlayModal.h"
#include "RaisedButton.h"
#include "TextField.h"
#include "ui/FlatButton.h"
#include "ui/LoadingIndicator.h"
#include "ui/OverlayModal.h"
#include "ui/RaisedButton.h"
#include "ui/TextField.h"
using namespace mtx::identifiers;

124
src/LoginPage.h Normal file
View file

@ -0,0 +1,124 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QLabel>
#include <QLayout>
#include <QSharedPointer>
#include <QWidget>
class FlatButton;
class LoadingIndicator;
class OverlayModal;
class RaisedButton;
class TextField;
namespace mtx {
namespace responses {
struct Login;
}
}
class LoginPage : public QWidget
{
Q_OBJECT
public:
LoginPage(QWidget *parent = 0);
void reset();
signals:
void backButtonClicked();
void loggingIn();
void errorOccurred();
//! Used to trigger the corresponding slot outside of the main thread.
void versionErrorCb(const QString &err);
void loginErrorCb(const QString &err);
void versionOkCb();
void loginOk(const mtx::responses::Login &res);
protected:
void paintEvent(QPaintEvent *event) override;
public slots:
// Displays errors produced during the login.
void loginError(const QString &msg) { error_label_->setText(msg); }
private slots:
// Callback for the back button.
void onBackButtonClicked();
// Callback for the login button.
void onLoginButtonClicked();
// Callback for probing the server found in the mxid
void onMatrixIdEntered();
// 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();
private:
bool isMatrixIdValid();
void checkHomeserverVersion();
std::string initialDeviceName()
{
#if defined(Q_OS_MAC)
return "nheko on macOS";
#elif defined(Q_OS_LINUX)
return "nheko on Linux";
#elif defined(Q_OS_WIN)
return "nheko on Windows";
#else
return "nheko";
#endif
}
QVBoxLayout *top_layout_;
QHBoxLayout *top_bar_layout_;
QHBoxLayout *logo_layout_;
QHBoxLayout *button_layout_;
QLabel *logo_;
QLabel *error_label_;
QHBoxLayout *serverLayout_;
QHBoxLayout *matrixidLayout_;
LoadingIndicator *spinner_;
QLabel *errorIcon_;
QString inferredServerAddress_;
FlatButton *back_button_;
RaisedButton *login_button_;
QWidget *form_widget_;
QHBoxLayout *form_wrapper_;
QVBoxLayout *form_layout_;
TextField *matrixid_input_;
TextField *password_input_;
TextField *serverInput_;
};

View file

@ -24,25 +24,25 @@
#include "ChatPage.h"
#include "Config.h"
#include "LoadingIndicator.h"
#include "Logging.hpp"
#include "Logging.h"
#include "LoginPage.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "OverlayModal.h"
#include "RegisterPage.h"
#include "SnackBar.h"
#include "TrayIcon.h"
#include "UserSettingsPage.h"
#include "WelcomePage.h"
#include "ui/LoadingIndicator.h"
#include "ui/OverlayModal.h"
#include "ui/SnackBar.h"
#include "dialogs/CreateRoom.h"
#include "dialogs/InviteUsers.h"
#include "dialogs/JoinRoom.h"
#include "dialogs/LeaveRoom.h"
#include "dialogs/Logout.h"
#include "dialogs/MemberList.hpp"
#include "dialogs/RoomSettings.hpp"
#include "dialogs/MemberList.h"
#include "dialogs/RoomSettings.h"
MainWindow *MainWindow::instance_ = nullptr;

174
src/MainWindow.h Normal file
View file

@ -0,0 +1,174 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <functional>
#include <QMainWindow>
#include <QSharedPointer>
#include <QStackedWidget>
#include <QSystemTrayIcon>
#include "LoginPage.h"
#include "RegisterPage.h"
#include "UserSettingsPage.h"
#include "WelcomePage.h"
class ChatPage;
class LoadingIndicator;
class OverlayModal;
class SnackBar;
class TrayIcon;
class UserSettings;
namespace mtx {
namespace requests {
struct CreateRoom;
}
}
namespace dialogs {
class CreateRoom;
class InviteUsers;
class JoinRoom;
class LeaveRoom;
class Logout;
class MemberList;
class ReCaptcha;
class RoomSettings;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
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(std::function<void()> callback);
void openRoomSettings(const QString &room_id = "");
void openMemberListDialog(const QString &room_id = "");
protected:
void closeEvent(QCloseEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void showEvent(QShowEvent *event) override;
private slots:
//! Show or hide the sidebars based on window's size.
void adjustSideBars();
//! Handle interaction with the tray icon.
void iconActivated(QSystemTrayIcon::ActivationReason reason);
//! Show the welcome page in the main window.
void showWelcomePage()
{
removeOverlayProgressBar();
pageStack_->addWidget(welcome_page_);
pageStack_->setCurrentWidget(welcome_page_);
}
//! Show the login page in the main window.
void showLoginPage()
{
pageStack_->addWidget(login_page_);
pageStack_->setCurrentWidget(login_page_);
}
//! Show the register page in the main window.
void showRegisterPage()
{
pageStack_->addWidget(register_page_);
pageStack_->setCurrentWidget(register_page_);
}
//! Show user settings page.
void showUserSettingsPage() { pageStack_->setCurrentWidget(userSettingsPage_); }
//! Show the chat page and start communicating with the given access token.
void showChatPage();
void showOverlayProgressBar();
void removeOverlayProgressBar();
private:
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_;
//! 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_;
//! Used to hide undefined states between page transitions.
QSharedPointer<OverlayModal> progressModal_;
QSharedPointer<LoadingIndicator> spinner_;
//! Tray icon that shows the unread message count.
TrayIcon *trayIcon_;
//! Notifications display.
QSharedPointer<SnackBar> snackBar_;
//! Leave room modal.
QSharedPointer<OverlayModal> leaveRoomModal_;
//! Leave room dialog.
QSharedPointer<dialogs::LeaveRoom> leaveRoomDialog_;
//! Invite users modal.
QSharedPointer<OverlayModal> inviteUsersModal_;
//! Invite users dialog.
QSharedPointer<dialogs::InviteUsers> inviteUsersDialog_;
//! Join room modal.
QSharedPointer<OverlayModal> joinRoomModal_;
//! Join room dialog.
QSharedPointer<dialogs::JoinRoom> joinRoomDialog_;
//! Create room modal.
QSharedPointer<OverlayModal> createRoomModal_;
//! Create room dialog.
QSharedPointer<dialogs::CreateRoom> createRoomDialog_;
//! Logout modal.
QSharedPointer<OverlayModal> logoutModal_;
//! Logout dialog.
QSharedPointer<dialogs::Logout> logoutDialog_;
//! Room settings modal.
QSharedPointer<OverlayModal> roomSettingsModal_;
//! Room settings dialog.
QSharedPointer<dialogs::RoomSettings> roomSettingsDialog_;
//! Member list modal.
QSharedPointer<OverlayModal> memberListModal_;
//! Member list dialog.
QSharedPointer<dialogs::MemberList> memberListDialog_;
};

30
src/MatrixClient.h Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include <QMetaType>
#include <QString>
#include <mtx/responses.hpp>
#include <mtxclient/http/client.hpp>
Q_DECLARE_METATYPE(mtx::responses::Login)
Q_DECLARE_METATYPE(mtx::responses::Messages)
Q_DECLARE_METATYPE(mtx::responses::Notifications)
Q_DECLARE_METATYPE(mtx::responses::Rooms)
Q_DECLARE_METATYPE(mtx::responses::Sync)
Q_DECLARE_METATYPE(mtx::responses::JoinedGroups)
Q_DECLARE_METATYPE(mtx::responses::GroupProfile)
Q_DECLARE_METATYPE(std::string)
Q_DECLARE_METATYPE(std::vector<std::string>)
Q_DECLARE_METATYPE(std::vector<QString>)
namespace http {
mtx::http::Client *
client();
bool
is_logged_in();
//! Initialize the http module
void
init();
}

View file

@ -1,7 +1,7 @@
#include "Olm.hpp"
#include "Olm.h"
#include "Cache.h"
#include "Logging.hpp"
#include "Logging.h"
#include "MatrixClient.h"
using namespace mtx::crypto;

86
src/Olm.h Normal file
View file

@ -0,0 +1,86 @@
#pragma once
#include <boost/optional.hpp>
#include <memory>
#include <mtx.hpp>
#include <mtxclient/crypto/client.hpp>
constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2";
namespace olm {
struct OlmMessage
{
std::string sender_key;
std::string sender;
using RecipientKey = std::string;
std::map<RecipientKey, mtx::events::msg::OlmCipherContent> ciphertext;
};
inline void
from_json(const nlohmann::json &obj, OlmMessage &msg)
{
if (obj.at("type") != "m.room.encrypted")
throw std::invalid_argument("invalid type for olm message");
if (obj.at("content").at("algorithm") != OLM_ALGO)
throw std::invalid_argument("invalid algorithm for olm message");
msg.sender = obj.at("sender");
msg.sender_key = obj.at("content").at("sender_key");
msg.ciphertext = obj.at("content")
.at("ciphertext")
.get<std::map<std::string, mtx::events::msg::OlmCipherContent>>();
}
mtx::crypto::OlmClient *
client();
void
handle_to_device_messages(const std::vector<nlohmann::json> &msgs);
nlohmann::json
try_olm_decryption(const std::string &sender_key,
const mtx::events::msg::OlmCipherContent &content);
void
handle_olm_message(const OlmMessage &msg);
//! Establish a new inbound megolm session with the decrypted payload from olm.
void
create_inbound_megolm_session(const std::string &sender,
const std::string &sender_key,
const nlohmann::json &payload);
void
handle_pre_key_olm_message(const std::string &sender,
const std::string &sender_key,
const mtx::events::msg::OlmCipherContent &content);
mtx::events::msg::Encrypted
encrypt_group_message(const std::string &room_id,
const std::string &device_id,
const std::string &body);
void
mark_keys_as_published();
//! Request the encryption keys from sender's device for the given event.
void
request_keys(const std::string &room_id, const std::string &event_id);
void
send_key_request_for(const std::string &room_id,
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &);
void
handle_key_request_message(const mtx::events::msg::KeyRequest &);
void
send_megolm_key_to_device(const std::string &user_id,
const std::string &device_id,
const json &payload);
} // namespace olm

View file

@ -23,6 +23,7 @@
#include <QtConcurrent>
#include "QuickSwitcher.h"
#include "SuggestionsPopup.h"
RoomSearchInput::RoomSearchInput(QWidget *parent)
: TextField(parent)

79
src/QuickSwitcher.h Normal file
View file

@ -0,0 +1,79 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QAbstractItemView>
#include <QKeyEvent>
#include <QVBoxLayout>
#include <QWidget>
#include "SuggestionsPopup.h"
#include "ui/TextField.h"
Q_DECLARE_METATYPE(std::vector<RoomSearchResult>)
class RoomSearchInput : public TextField
{
Q_OBJECT
public:
explicit RoomSearchInput(QWidget *parent = nullptr);
signals:
void selectNextCompletion();
void selectPreviousCompletion();
void hiding();
protected:
void keyPressEvent(QKeyEvent *event) override;
void hideEvent(QHideEvent *event) override;
bool focusNextPrevChild(bool) override { return false; };
};
class QuickSwitcher : public QWidget
{
Q_OBJECT
public:
QuickSwitcher(QWidget *parent = nullptr);
signals:
void closing();
void roomSelected(const QString &roomid);
void queryResults(const std::vector<RoomSearchResult> &rooms);
protected:
void keyPressEvent(QKeyEvent *event) override;
void showEvent(QShowEvent *) override { roomSearch_->setFocus(); }
void paintEvent(QPaintEvent *event) override;
private:
void reset()
{
emit closing();
roomSearch_->clear();
}
// Current highlighted selection from the completer.
int selection_ = -1;
QVBoxLayout *topLayout_;
RoomSearchInput *roomSearch_;
//! Autocomplete popup box with the room suggestions.
SuggestionsPopup popup_;
};

View file

@ -19,15 +19,15 @@
#include <QTimer>
#include "Config.h"
#include "FlatButton.h"
#include "Logging.hpp"
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "RaisedButton.h"
#include "RegisterPage.h"
#include "TextField.h"
#include "ui/FlatButton.h"
#include "ui/RaisedButton.h"
#include "ui/TextField.h"
#include "dialogs/ReCaptcha.hpp"
#include "dialogs/ReCaptcha.h"
RegisterPage::RegisterPage(QWidget *parent)
: QWidget(parent)

84
src/RegisterPage.h Normal file
View file

@ -0,0 +1,84 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QLabel>
#include <QLayout>
#include <QSharedPointer>
#include <memory>
class FlatButton;
class RaisedButton;
class TextField;
namespace dialogs {
class ReCaptcha;
}
class RegisterPage : public QWidget
{
Q_OBJECT
public:
RegisterPage(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *event) override;
signals:
void backButtonClicked();
void errorOccurred();
void registering();
void registerOk();
void registerErrorCb(const QString &msg);
void registrationFlow(const std::string &user,
const std::string &pass,
const std::string &session);
private slots:
void onBackButtonClicked();
void onRegisterButtonClicked();
// Display registration specific errors to the user.
void registerError(const QString &msg);
private:
QVBoxLayout *top_layout_;
QHBoxLayout *back_layout_;
QHBoxLayout *logo_layout_;
QHBoxLayout *button_layout_;
QLabel *logo_;
QLabel *error_label_;
FlatButton *back_button_;
RaisedButton *register_button_;
QWidget *form_widget_;
QHBoxLayout *form_wrapper_;
QVBoxLayout *form_layout_;
TextField *username_input_;
TextField *password_input_;
TextField *password_confirmation_;
TextField *server_input_;
//! ReCaptcha dialog.
std::shared_ptr<dialogs::ReCaptcha> captchaDialog_;
};

View file

@ -24,12 +24,12 @@
#include "Cache.h"
#include "Config.h"
#include "Menu.h"
#include "Ripple.h"
#include "RippleOverlay.h"
#include "RoomInfoListItem.h"
#include "Theme.h"
#include "Utils.h"
#include "ui/Menu.h"
#include "ui/Ripple.h"
#include "ui/RippleOverlay.h"
#include "ui/Theme.h"
constexpr int MaxUnreadCountDisplayed = 99;

204
src/RoomInfoListItem.h Normal file
View file

@ -0,0 +1,204 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QAction>
#include <QDateTime>
#include <QSharedPointer>
#include <QWidget>
#include "Cache.h"
#include <mtx/responses.hpp>
class Menu;
class RippleOverlay;
class RoomInfoListItem : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor highlightedBackgroundColor READ highlightedBackgroundColor WRITE
setHighlightedBackgroundColor)
Q_PROPERTY(
QColor hoverBackgroundColor READ hoverBackgroundColor WRITE setHoverBackgroundColor)
Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
Q_PROPERTY(QColor avatarBgColor READ avatarBgColor WRITE setAvatarBgColor)
Q_PROPERTY(QColor avatarFgColor READ avatarFgColor WRITE setAvatarFgColor)
Q_PROPERTY(QColor bubbleBgColor READ bubbleBgColor WRITE setBubbleBgColor)
Q_PROPERTY(QColor bubbleFgColor READ bubbleFgColor WRITE setBubbleFgColor)
Q_PROPERTY(QColor titleColor READ titleColor WRITE setTitleColor)
Q_PROPERTY(QColor subtitleColor READ subtitleColor WRITE setSubtitleColor)
Q_PROPERTY(QColor timestampColor READ timestampColor WRITE setTimestampColor)
Q_PROPERTY(QColor highlightedTimestampColor READ highlightedTimestampColor WRITE
setHighlightedTimestampColor)
Q_PROPERTY(
QColor highlightedTitleColor READ highlightedTitleColor WRITE setHighlightedTitleColor)
Q_PROPERTY(QColor highlightedSubtitleColor READ highlightedSubtitleColor WRITE
setHighlightedSubtitleColor)
Q_PROPERTY(QColor btnColor READ btnColor WRITE setBtnColor)
Q_PROPERTY(QColor btnTextColor READ btnTextColor WRITE setBtnTextColor)
public:
RoomInfoListItem(QString room_id, RoomInfo info, QWidget *parent = 0);
void updateUnreadMessageCount(int count);
void clearUnreadMessageCount() { updateUnreadMessageCount(0); };
QString roomId() { return roomId_; }
bool isPressed() const { return isPressed_; }
int unreadMessageCount() const { return unreadMsgCount_; }
void setAvatar(const QImage &avatar_image);
void setDescriptionMessage(const DescInfo &info);
DescInfo lastMessageInfo() const { return lastMsgInfo_; }
QColor highlightedBackgroundColor() const { return highlightedBackgroundColor_; }
QColor hoverBackgroundColor() const { return hoverBackgroundColor_; }
QColor backgroundColor() const { return backgroundColor_; }
QColor avatarBgColor() const { return avatarBgColor_; }
QColor avatarFgColor() const { return avatarFgColor_; }
QColor highlightedTitleColor() const { return highlightedTitleColor_; }
QColor highlightedSubtitleColor() const { return highlightedSubtitleColor_; }
QColor highlightedTimestampColor() const { return highlightedTimestampColor_; }
QColor titleColor() const { return titleColor_; }
QColor subtitleColor() const { return subtitleColor_; }
QColor timestampColor() const { return timestampColor_; }
QColor btnColor() const { return btnColor_; }
QColor btnTextColor() const { return btnTextColor_; }
QColor bubbleFgColor() const { return bubbleFgColor_; }
QColor bubbleBgColor() const { return bubbleBgColor_; }
void setHighlightedBackgroundColor(QColor &color) { highlightedBackgroundColor_ = color; }
void setHoverBackgroundColor(QColor &color) { hoverBackgroundColor_ = color; }
void setBackgroundColor(QColor &color) { backgroundColor_ = color; }
void setTimestampColor(QColor &color) { timestampColor_ = color; }
void setAvatarFgColor(QColor &color) { avatarFgColor_ = color; }
void setAvatarBgColor(QColor &color) { avatarBgColor_ = color; }
void setHighlightedTitleColor(QColor &color) { highlightedTitleColor_ = color; }
void setHighlightedSubtitleColor(QColor &color) { highlightedSubtitleColor_ = color; }
void setHighlightedTimestampColor(QColor &color) { highlightedTimestampColor_ = color; }
void setTitleColor(QColor &color) { titleColor_ = color; }
void setSubtitleColor(QColor &color) { subtitleColor_ = color; }
void setBtnColor(QColor &color) { btnColor_ = color; }
void setBtnTextColor(QColor &color) { btnTextColor_ = color; }
void setBubbleFgColor(QColor &color) { bubbleFgColor_ = color; }
void setBubbleBgColor(QColor &color) { bubbleBgColor_ = color; }
void setRoomName(const QString &name) { roomName_ = name; }
void setRoomType(bool isInvite)
{
if (isInvite)
roomType_ = RoomType::Invited;
else
roomType_ = RoomType::Joined;
}
bool isInvite() { return roomType_ == RoomType::Invited; }
signals:
void clicked(const QString &room_id);
void leaveRoom(const QString &room_id);
void acceptInvite(const QString &room_id);
void declineInvite(const QString &room_id);
public slots:
void setPressedState(bool state);
protected:
void mousePressEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
private:
void init(QWidget *parent);
QString roomName() { return roomName_; }
RippleOverlay *ripple_overlay_;
enum class RoomType
{
Joined,
Invited,
};
RoomType roomType_ = RoomType::Joined;
// State information for the invited rooms.
mtx::responses::InvitedRoom invitedRoom_;
QString roomId_;
QString roomName_;
DescInfo lastMsgInfo_;
QPixmap roomAvatar_;
Menu *menu_;
QAction *leaveRoom_;
bool isPressed_ = false;
int unreadMsgCount_ = 0;
QColor highlightedBackgroundColor_;
QColor hoverBackgroundColor_;
QColor backgroundColor_;
QColor highlightedTitleColor_;
QColor highlightedSubtitleColor_;
QColor titleColor_;
QColor subtitleColor_;
QColor btnColor_;
QColor btnTextColor_;
QRectF acceptBtnRegion_;
QRectF declineBtnRegion_;
// Fonts
QFont bubbleFont_;
QFont font_;
QFont headingFont_;
QFont timestampFont_;
QFont usernameFont_;
QFont unreadCountFont_;
int bubbleDiameter_;
QColor timestampColor_;
QColor highlightedTimestampColor_;
QColor avatarBgColor_;
QColor avatarFgColor_;
QColor bubbleBgColor_;
QColor bubbleFgColor_;
};

View file

@ -21,14 +21,14 @@
#include <QTimer>
#include "Cache.h"
#include "Logging.hpp"
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "OverlayModal.h"
#include "RoomInfoListItem.h"
#include "RoomList.h"
#include "UserSettingsPage.h"
#include "Utils.h"
#include "ui/OverlayModal.h"
RoomList::RoomList(QSharedPointer<UserSettings> userSettings, QWidget *parent)
: QWidget(parent)

108
src/RoomList.h Normal file
View file

@ -0,0 +1,108 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QPushButton>
#include <QScrollArea>
#include <QSharedPointer>
#include <QVBoxLayout>
#include <QWidget>
#include <mtx.hpp>
class LeaveRoomDialog;
class OverlayModal;
class RoomInfoListItem;
class Sync;
class UserSettings;
struct DescInfo;
struct RoomInfo;
class RoomList : public QWidget
{
Q_OBJECT
public:
RoomList(QSharedPointer<UserSettings> userSettings, QWidget *parent = 0);
void initialize(const QMap<QString, RoomInfo> &info);
void sync(const std::map<QString, RoomInfo> &info);
void clear() { rooms_.clear(); };
void updateAvatar(const QString &room_id, const QString &url);
void addRoom(const QString &room_id, const RoomInfo &info);
void addInvitedRoom(const QString &room_id, const RoomInfo &info);
void removeRoom(const QString &room_id, bool reset);
void setFilterRooms(bool filterRooms);
void setRoomFilter(std::vector<QString> room_ids);
void updateRoom(const QString &room_id, const RoomInfo &info);
void cleanupInvites(const std::map<QString, bool> &invites);
signals:
void roomChanged(const QString &room_id);
void totalUnreadMessageCountUpdated(int count);
void acceptInvite(const QString &room_id);
void declineInvite(const QString &room_id);
void roomAvatarChanged(const QString &room_id, const QPixmap &img);
void joinRoom(const QString &room_id);
void updateRoomAvatarCb(const QString &room_id, const QPixmap &img);
public slots:
void updateRoomAvatar(const QString &roomid, const QPixmap &img);
void highlightSelectedRoom(const QString &room_id);
void updateUnreadMessageCount(const QString &roomid, int count);
void updateRoomDescription(const QString &roomid, const DescInfo &info);
void closeJoinRoomDialog(bool isJoining, QString roomAlias);
protected:
void paintEvent(QPaintEvent *event) override;
void leaveEvent(QEvent *event) override;
private slots:
void sortRoomsByLastMessage();
private:
//! Return the first non-null room.
std::pair<QString, QSharedPointer<RoomInfoListItem>> firstRoom() const;
void calculateUnreadMessageCount();
bool roomExists(const QString &room_id) { return rooms_.find(room_id) != rooms_.end(); }
bool filterItemExists(const QString &id)
{
return std::find(roomFilter_.begin(), roomFilter_.end(), id) != roomFilter_.end();
}
QVBoxLayout *topLayout_;
QVBoxLayout *contentsLayout_;
QScrollArea *scrollArea_;
QWidget *scrollAreaContents_;
QPushButton *joinRoomButton_;
OverlayModal *joinRoomModal_;
std::map<QString, QSharedPointer<RoomInfoListItem>> rooms_;
QString selectedRoom_;
//! Which rooms to include in the room list.
std::vector<QString> roomFilter_;
QSharedPointer<UserSettings> userSettings_;
bool isSortPending_ = false;
};

31
src/RunGuard.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
//
// Taken from
// https://stackoverflow.com/questions/5006547/qt-best-practice-for-a-single-instance-app-protection
//
#include <QObject>
#include <QSharedMemory>
#include <QSystemSemaphore>
class RunGuard
{
public:
RunGuard(const QString &key);
~RunGuard();
bool isAnotherRunning();
bool tryToRun();
void release();
private:
const QString key;
const QString memLockKey;
const QString sharedmemKey;
QSharedMemory sharedMem;
QSystemSemaphore memLock;
Q_DISABLE_COPY(RunGuard)
};

View file

@ -5,9 +5,11 @@
#include "Config.h"
#include "MainWindow.h"
#include "OverlayModal.h"
#include "SideBarActions.h"
#include "Theme.h"
#include "ui/FlatButton.h"
#include "ui/Menu.h"
#include "ui/OverlayModal.h"
#include "ui/Theme.h"
SideBarActions::SideBarActions(QWidget *parent)
: QWidget{parent}

50
src/SideBarActions.h Normal file
View file

@ -0,0 +1,50 @@
#pragma once
#include <QAction>
#include <QHBoxLayout>
#include <QResizeEvent>
#include <QWidget>
namespace mtx {
namespace requests {
struct CreateRoom;
}
}
class Menu;
class FlatButton;
class SideBarActions : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor)
public:
SideBarActions(QWidget *parent = nullptr);
QColor borderColor() const { return borderColor_; }
void setBorderColor(QColor &color) { borderColor_ = color; }
signals:
void showSettings();
void joinRoom(const QString &room);
void createRoom(const mtx::requests::CreateRoom &request);
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private:
QHBoxLayout *layout_;
Menu *addMenu_;
QAction *createRoomAction_;
QAction *joinRoomAction_;
FlatButton *settingsBtn_;
FlatButton *createRoomBtn_;
FlatButton *joinRoomBtn_;
QColor borderColor_;
};

View file

@ -23,7 +23,7 @@
#include "Config.h"
#include "Splitter.h"
#include "Theme.h"
#include "ui/Theme.h"
constexpr auto MaxWidth = (1 << 24) - 1;

46
src/Splitter.h Normal file
View file

@ -0,0 +1,46 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QSplitter>
class Splitter : public QSplitter
{
Q_OBJECT
public:
explicit Splitter(QWidget *parent = nullptr);
~Splitter();
void restoreSizes(int fallback);
public slots:
void hideSidebar();
void showFullRoomList();
void showChatView();
signals:
void hiddenSidebar();
private:
void onSplitterMoved(int pos, int index);
int moveEventLimit_ = 50;
int leftMoveCount_ = 0;
int rightMoveCount_ = 0;
};

View file

@ -1,14 +1,13 @@
#include "Avatar.h"
#include "AvatarProvider.h"
#include "Config.h"
#include "DropShadow.h"
#include "SuggestionsPopup.hpp"
#include "Utils.h"
#include <QPaintEvent>
#include <QPainter>
#include <QStyleOption>
#include "Config.h"
#include "SuggestionsPopup.h"
#include "Utils.h"
#include "ui/Avatar.h"
#include "ui/DropShadow.h"
constexpr int PopupHMargin = 4;
constexpr int PopupItemMargin = 3;

147
src/SuggestionsPopup.h Normal file
View file

@ -0,0 +1,147 @@
#pragma once
#include <QHBoxLayout>
#include <QLabel>
#include <QPoint>
#include <QWidget>
#include "AvatarProvider.h"
#include "Cache.h"
#include "ChatPage.h"
class Avatar;
struct SearchResult;
class PopupItem : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor hoverColor READ hoverColor WRITE setHoverColor)
Q_PROPERTY(bool hovering READ hovering WRITE setHovering)
public:
PopupItem(QWidget *parent);
QString selectedText() const { return QString(); }
QColor hoverColor() const { return hoverColor_; }
void setHoverColor(QColor &color) { hoverColor_ = color; }
bool hovering() const { return hovering_; }
void setHovering(const bool hover) { hovering_ = hover; };
protected:
void paintEvent(QPaintEvent *event) override;
signals:
void clicked(const QString &text);
protected:
QHBoxLayout *topLayout_;
Avatar *avatar_;
QColor hoverColor_;
//! Set if the item is currently being
//! hovered during tab completion (cycling).
bool hovering_;
};
class UserItem : public PopupItem
{
Q_OBJECT
public:
UserItem(QWidget *parent, const QString &user_id);
QString selectedText() const { return userId_; }
void updateItem(const QString &user_id);
protected:
void mousePressEvent(QMouseEvent *event) override;
private:
void resolveAvatar(const QString &user_id);
QLabel *userName_;
QString userId_;
};
class RoomItem : public PopupItem
{
Q_OBJECT
public:
RoomItem(QWidget *parent, const RoomSearchResult &res);
QString selectedText() const { return roomId_; }
void updateItem(const RoomSearchResult &res);
protected:
void mousePressEvent(QMouseEvent *event) override;
private:
QLabel *roomName_;
QString roomId_;
RoomSearchResult info_;
};
class SuggestionsPopup : public QWidget
{
Q_OBJECT
public:
explicit SuggestionsPopup(QWidget *parent = nullptr);
template<class Item>
void selectHoveredSuggestion()
{
const auto item = layout_->itemAt(selectedItem_);
if (!item)
return;
const auto &widget = qobject_cast<Item *>(item->widget());
emit itemSelected(
Cache::displayName(ChatPage::instance()->currentRoom(), widget->selectedText()));
resetSelection();
}
public slots:
void addUsers(const QVector<SearchResult> &users);
void addRooms(const std::vector<RoomSearchResult> &rooms);
//! Move to the next available suggestion item.
void selectNextSuggestion();
//! Move to the previous available suggestion item.
void selectPreviousSuggestion();
//! Remove hovering from all items.
void resetHovering();
//! Set hovering to the item in the given layout position.
void setHovering(int pos);
protected:
void paintEvent(QPaintEvent *event) override;
signals:
void itemSelected(const QString &user);
private:
void hoverSelection();
void resetSelection() { selectedItem_ = -1; }
void selectFirstItem() { selectedItem_ = 0; }
void selectLastItem() { selectedItem_ = layout_->count() - 1; }
void removeLayoutItemsAfter(size_t startingPos)
{
size_t posToRemove = layout_->count() - 1;
QLayoutItem *item;
while (startingPos <= posToRemove && (item = layout_->takeAt(posToRemove)) != 0) {
delete item->widget();
delete item;
posToRemove = layout_->count() - 1;
}
}
QVBoxLayout *layout_;
//! Counter for tab completion (cycling).
int selectedItem_ = -1;
};

View file

@ -36,6 +36,8 @@
#include "Config.h"
#include "TextInputWidget.h"
#include "Utils.h"
#include "ui/FlatButton.h"
#include "ui/LoadingIndicator.h"
static constexpr size_t INPUT_HISTORY_SIZE = 127;
static constexpr int MAX_TEXTINPUT_HEIGHT = 120;

183
src/TextInputWidget.h Normal file
View file

@ -0,0 +1,183 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <deque>
#include <iterator>
#include <map>
#include <QApplication>
#include <QDebug>
#include <QHBoxLayout>
#include <QPaintEvent>
#include <QTextEdit>
#include <QWidget>
#include "SuggestionsPopup.h"
#include "dialogs/PreviewUploadOverlay.h"
#include "emoji/PickButton.h"
namespace dialogs {
class PreviewUploadOverlay;
}
struct SearchResult;
class FlatButton;
class LoadingIndicator;
class FilteredTextEdit : public QTextEdit
{
Q_OBJECT
public:
explicit FilteredTextEdit(QWidget *parent = nullptr);
void stopTyping();
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
void submit();
signals:
void heightChanged(int height);
void startedTyping();
void stoppedTyping();
void startedUpload();
void message(QString);
void command(QString name, QString args);
void image(QSharedPointer<QIODevice> data, const QString &filename);
void audio(QSharedPointer<QIODevice> data, const QString &filename);
void video(QSharedPointer<QIODevice> data, const QString &filename);
void file(QSharedPointer<QIODevice> data, const QString &filename);
//! Trigger the suggestion popup.
void showSuggestions(const QString &query);
void resultsRetrieved(const QVector<SearchResult> &results);
void selectNextSuggestion();
void selectPreviousSuggestion();
void selectHoveredSuggestion();
public slots:
void showResults(const QVector<SearchResult> &results);
protected:
void keyPressEvent(QKeyEvent *event) override;
bool canInsertFromMimeData(const QMimeData *source) const override;
void insertFromMimeData(const QMimeData *source) override;
void focusOutEvent(QFocusEvent *event) override
{
popup_.hide();
QTextEdit::focusOutEvent(event);
}
private:
std::deque<QString> true_history_, working_history_;
size_t history_index_;
QTimer *typingTimer_;
SuggestionsPopup popup_;
void closeSuggestions() { popup_.hide(); }
void resetAnchor() { atTriggerPosition_ = -1; }
QString query()
{
auto cursor = textCursor();
cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
return cursor.selectedText();
}
dialogs::PreviewUploadOverlay previewDialog_;
//! Latest position of the '@' character that triggers the username completer.
int atTriggerPosition_ = -1;
void textChanged();
void uploadData(const QByteArray data, const QString &media, const QString &filename);
void afterCompletion(int);
void showPreview(const QMimeData *source, const QStringList &formats);
};
class TextInputWidget : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor)
public:
TextInputWidget(QWidget *parent = 0);
void stopTyping();
QColor borderColor() const { return borderColor_; }
void setBorderColor(QColor &color) { borderColor_ = color; }
void disableInput()
{
input_->setEnabled(false);
input_->setPlaceholderText(tr("Connection lost. Nheko is trying to re-connect..."));
}
void enableInput()
{
input_->setEnabled(true);
input_->setPlaceholderText(tr("Write a message..."));
}
public slots:
void openFileSelection();
void hideUploadSpinner();
void focusLineEdit() { input_->setFocus(); }
void addReply(const QString &username, const QString &msg);
private slots:
void addSelectedEmoji(const QString &emoji);
signals:
void sendTextMessage(QString msg);
void sendEmoteMessage(QString msg);
void uploadImage(const QSharedPointer<QIODevice> data, const QString &filename);
void uploadFile(const QSharedPointer<QIODevice> data, const QString &filename);
void uploadAudio(const QSharedPointer<QIODevice> data, const QString &filename);
void uploadVideo(const QSharedPointer<QIODevice> data, const QString &filename);
void sendJoinRoomRequest(const QString &room);
void startedTyping();
void stoppedTyping();
protected:
void focusInEvent(QFocusEvent *event) override;
void paintEvent(QPaintEvent *) override;
private:
void showUploadSpinner();
void command(QString name, QString args);
QHBoxLayout *topLayout_;
FilteredTextEdit *input_;
LoadingIndicator *spinner_;
FlatButton *sendFileBtn_;
FlatButton *sendMessageBtn_;
emoji::PickButton *emojiBtn_;
QColor borderColor_;
};

View file

@ -18,14 +18,14 @@
#include <QDebug>
#include <QStyleOption>
#include "Avatar.h"
#include "Config.h"
#include "FlatButton.h"
#include "MainWindow.h"
#include "Menu.h"
#include "OverlayModal.h"
#include "TopRoomBar.h"
#include "Utils.h"
#include "ui/Avatar.h"
#include "ui/FlatButton.h"
#include "ui/Menu.h"
#include "ui/OverlayModal.h"
TopRoomBar::TopRoomBar(QWidget *parent)
: QWidget(parent)

107
src/TopRoomBar.h Normal file
View file

@ -0,0 +1,107 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QAction>
#include <QIcon>
#include <QImage>
#include <QLabel>
#include <QPaintEvent>
#include <QPainter>
#include <QPen>
#include <QSharedPointer>
#include <QStyle>
#include <QStyleOption>
#include <QVBoxLayout>
class Avatar;
class FlatButton;
class Menu;
class OverlayModal;
class TopRoomBar : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor)
public:
TopRoomBar(QWidget *parent = 0);
void updateRoomAvatar(const QImage &avatar_image);
void updateRoomAvatar(const QIcon &icon);
void updateRoomName(const QString &name);
void updateRoomTopic(QString topic);
void updateRoomAvatarFromName(const QString &name);
void reset();
QColor borderColor() const { return borderColor_; }
void setBorderColor(QColor &color) { borderColor_ = color; }
public slots:
//! Add a "back-arrow" button that can switch to roomlist only view.
void enableBackButton();
//! Replace the "back-arrow" button with the avatar of the room.
void disableBackButton();
signals:
void inviteUsers(QStringList users);
void showRoomList();
protected:
void mousePressEvent(QMouseEvent *) override
{
if (roomSettings_ != nullptr)
roomSettings_->trigger();
}
void paintEvent(QPaintEvent *) override
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
p.setPen(QPen(borderColor()));
p.drawLine(QPointF(0, height() - p.pen().widthF()),
QPointF(width(), height() - p.pen().widthF()));
}
private:
QHBoxLayout *topLayout_ = nullptr;
QVBoxLayout *textLayout_ = nullptr;
QLabel *nameLabel_ = nullptr;
QLabel *topicLabel_ = nullptr;
Menu *menu_;
QAction *leaveRoom_ = nullptr;
QAction *roomMembers_ = nullptr;
QAction *roomSettings_ = nullptr;
QAction *inviteUsers_ = nullptr;
FlatButton *settingsBtn_;
FlatButton *backBtn_;
Avatar *avatar_;
int buttonSize_;
QColor borderColor_;
};

59
src/TrayIcon.h Normal file
View file

@ -0,0 +1,59 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QAction>
#include <QIcon>
#include <QIconEngine>
#include <QPainter>
#include <QRect>
#include <QSystemTrayIcon>
class MsgCountComposedIcon : public QIconEngine
{
public:
MsgCountComposedIcon(const QString &filename);
virtual void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state);
virtual QIconEngine *clone() const;
virtual QList<QSize> availableSizes(QIcon::Mode mode, QIcon::State state) const;
virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state);
int msgCount = 0;
private:
const int BubbleDiameter = 17;
QIcon icon_;
};
class TrayIcon : public QSystemTrayIcon
{
Q_OBJECT
public:
TrayIcon(const QString &filename, QWidget *parent);
public slots:
void setUnreadCount(int count);
private:
QAction *viewAction_;
QAction *quitAction_;
MsgCountComposedIcon *icon_;
};

21
src/TypingDisplay.h Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include <QPaintEvent>
#include <QWidget>
class TypingDisplay : public QWidget
{
Q_OBJECT
public:
TypingDisplay(QWidget *parent = nullptr);
void setUsers(const QStringList &user_ids);
protected:
void paintEvent(QPaintEvent *event) override;
private:
QString text_;
int leftPadding_;
};

View file

@ -17,12 +17,12 @@
#include <QTimer>
#include "Avatar.h"
#include "Config.h"
#include "FlatButton.h"
#include "MainWindow.h"
#include "OverlayModal.h"
#include "UserInfoWidget.h"
#include "ui/Avatar.h"
#include "ui/FlatButton.h"
#include "ui/OverlayModal.h"
UserInfoWidget::UserInfoWidget(QWidget *parent)
: QWidget(parent)

73
src/UserInfoWidget.h Normal file
View file

@ -0,0 +1,73 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QLabel>
#include <QLayout>
class Avatar;
class FlatButton;
class OverlayModal;
class UserInfoWidget : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor)
public:
UserInfoWidget(QWidget *parent = 0);
void setAvatar(const QImage &img);
void setDisplayName(const QString &name);
void setUserId(const QString &userid);
void reset();
QColor borderColor() const { return borderColor_; }
void setBorderColor(QColor &color) { borderColor_ = color; }
signals:
void logout();
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private:
Avatar *userAvatar_;
QHBoxLayout *topLayout_;
QHBoxLayout *avatarLayout_;
QVBoxLayout *textLayout_;
QHBoxLayout *buttonLayout_;
FlatButton *logoutButton_;
QLabel *displayNameLabel_;
QLabel *userIdLabel_;
QString display_name_;
QString user_id_;
QImage avatar_image_;
int logoutButtonSize_;
QColor borderColor_;
};

View file

@ -24,11 +24,11 @@
#include <QSettings>
#include "Config.h"
#include "FlatButton.h"
#include "UserSettingsPage.h"
#include <ToggleButton.h>
#include "ui/FlatButton.h"
#include "ui/ToggleButton.h"
#include "version.hpp"
#include "version.h"
UserSettings::UserSettings() { load(); }

148
src/UserSettingsPage.h Normal file
View file

@ -0,0 +1,148 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QComboBox>
#include <QFrame>
#include <QLayout>
#include <QSharedPointer>
#include <QWidget>
class Toggle;
constexpr int OptionMargin = 6;
constexpr int LayoutTopMargin = 50;
constexpr int LayoutBottomMargin = LayoutTopMargin;
class UserSettings : public QObject
{
Q_OBJECT
public:
UserSettings();
void save();
void load();
void applyTheme();
void setTheme(QString theme);
void setTray(bool state)
{
isTrayEnabled_ = state;
save();
};
void setStartInTray(bool state)
{
isStartInTrayEnabled_ = state;
save();
};
void setRoomOrdering(bool state)
{
isOrderingEnabled_ = state;
save();
};
void setGroupView(bool state)
{
if (isGroupViewEnabled_ != state)
emit groupViewStateChanged(state);
isGroupViewEnabled_ = state;
save();
};
void setReadReceipts(bool state)
{
isReadReceiptsEnabled_ = state;
save();
}
void setTypingNotifications(bool state)
{
isTypingNotificationsEnabled_ = state;
save();
};
QString theme() const { return !theme_.isEmpty() ? theme_ : "light"; }
bool isTrayEnabled() const { return isTrayEnabled_; }
bool isStartInTrayEnabled() const { return isStartInTrayEnabled_; }
bool isOrderingEnabled() const { return isOrderingEnabled_; }
bool isGroupViewEnabled() const { return isGroupViewEnabled_; }
bool isTypingNotificationsEnabled() const { return isTypingNotificationsEnabled_; }
bool isReadReceiptsEnabled() const { return isReadReceiptsEnabled_; }
signals:
void groupViewStateChanged(bool state);
private:
QString theme_;
bool isTrayEnabled_;
bool isStartInTrayEnabled_;
bool isOrderingEnabled_;
bool isGroupViewEnabled_;
bool isTypingNotificationsEnabled_;
bool isReadReceiptsEnabled_;
};
class HorizontalLine : public QFrame
{
Q_OBJECT
public:
HorizontalLine(QWidget *parent = nullptr);
};
class UserSettingsPage : public QWidget
{
Q_OBJECT
public:
UserSettingsPage(QSharedPointer<UserSettings> settings, QWidget *parent = 0);
protected:
void showEvent(QShowEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent *event) override;
signals:
void moveBack();
void trayOptionChanged(bool value);
private:
void restoreThemeCombo() const;
// Layouts
QVBoxLayout *topLayout_;
QVBoxLayout *mainLayout_;
QHBoxLayout *topBarLayout_;
// Shared settings object.
QSharedPointer<UserSettings> settings_;
Toggle *trayToggle_;
Toggle *startInTrayToggle_;
Toggle *roomOrderToggle_;
Toggle *groupViewToggle_;
Toggle *typingNotifications_;
Toggle *readReceipts_;
QComboBox *themeCombo_;
int sideMargin_ = 0;
};

194
src/Utils.h Normal file
View file

@ -0,0 +1,194 @@
#pragma once
#include "Cache.h"
#include "RoomInfoListItem.h"
#include "timeline/widgets/AudioItem.h"
#include "timeline/widgets/FileItem.h"
#include "timeline/widgets/ImageItem.h"
#include "timeline/widgets/VideoItem.h"
#include <QDateTime>
#include <QPixmap>
#include <mtx/events/collections.hpp>
namespace utils {
using TimelineEvent = mtx::events::collections::TimelineEvents;
//! Human friendly timestamp representation.
QString
descriptiveTime(const QDateTime &then);
//! Generate a message description from the event to be displayed
//! in the RoomList.
DescInfo
getMessageDescription(const TimelineEvent &event, const QString &localUser, const QString &room_id);
//! Get the first character of a string, taking into account that
//! surrogate pairs might be in use.
QString
firstChar(const QString &input);
//! Get a human readable file size with the appropriate units attached.
QString
humanReadableFileSize(uint64_t bytes);
QString
event_body(const mtx::events::collections::TimelineEvents &event);
//! Match widgets/events with a description message.
template<class T>
QString
messageDescription(const QString &username = "", const QString &body = "")
{
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 Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
if (std::is_same<T, AudioItem>::value || std::is_same<T, Audio>::value)
return QString("sent an audio clip");
else if (std::is_same<T, ImageItem>::value || std::is_same<T, Image>::value)
return QString("sent an image");
else if (std::is_same<T, FileItem>::value || std::is_same<T, File>::value)
return QString("sent a file");
else if (std::is_same<T, VideoItem>::value || std::is_same<T, Video>::value)
return QString("sent a video clip");
else if (std::is_same<T, StickerItem>::value || std::is_same<T, Sticker>::value)
return QString("sent a sticker");
else if (std::is_same<T, Notice>::value)
return QString("sent a notification");
else if (std::is_same<T, Text>::value)
return QString(": %1").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)
return QString("sent an encrypted message");
}
template<class T, class Event>
DescInfo
createDescriptionInfo(const Event &event, const QString &localUser, const QString &room_id)
{
using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
const auto msg = mpark::get<T>(event);
const auto sender = QString::fromStdString(msg.sender);
const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
bool isText = std::is_same<T, Text>::value;
bool isEmote = std::is_same<T, Emote>::value;
return DescInfo{
isEmote ? "" : (sender == localUser ? "You" : username),
sender,
(isText || isEmote)
? messageDescription<T>(username, QString::fromStdString(msg.content.body).trimmed())
: QString(" %1").arg(messageDescription<T>()),
utils::descriptiveTime(ts),
ts};
}
//! Scale down an image to fit to the given width & height limitations.
template<class ImageType>
ImageType
scaleDown(uint64_t max_width, uint64_t max_height, const ImageType &source)
{
if (source.isNull())
return QPixmap();
auto width_ratio = (double)max_width / (double)source.width();
auto height_ratio = (double)max_height / (double)source.height();
auto min_aspect_ratio = std::min(width_ratio, height_ratio);
int final_width = 0;
int final_height = 0;
if (min_aspect_ratio > 1) {
final_width = source.width();
final_height = source.height();
} else {
final_width = source.width() * min_aspect_ratio;
final_height = source.height() * min_aspect_ratio;
}
return source.scaled(
final_width, final_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
//! Delete items in a container based on a predicate.
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;
}
}
inline uint64_t
event_timestamp(const mtx::events::collections::TimelineEvents &event)
{
return mpark::visit([](auto msg) { return msg.origin_server_ts; }, event);
}
inline nlohmann::json
serialize_event(const mtx::events::collections::TimelineEvents &event)
{
return mpark::visit([](auto msg) { return json(msg); }, event);
}
inline mtx::events::EventType
event_type(const mtx::events::collections::TimelineEvents &event)
{
return mpark::visit([](auto msg) { return msg.type; }, event);
}
inline std::string
event_id(const mtx::events::collections::TimelineEvents &event)
{
return mpark::visit([](auto msg) { return msg.event_id; }, event);
}
inline QString
eventId(const mtx::events::collections::TimelineEvents &event)
{
return QString::fromStdString(event_id(event));
}
inline QString
event_sender(const mtx::events::collections::TimelineEvents &event)
{
return mpark::visit([](auto msg) { return QString::fromStdString(msg.sender); }, event);
}
template<class T>
QString
message_body(const mtx::events::collections::TimelineEvents &event)
{
return QString::fromStdString(mpark::get<T>(event).content.body);
}
//! Calculate the Levenshtein distance between two strings with character skipping.
int
levenshtein_distance(const std::string &s1, const std::string &s2);
QPixmap
scaleImageToPixmap(const QImage &img, int size);
//! Convert a Content Matrix URI to an HTTP link.
QString
mxcToHttp(const QUrl &url, const QString &server, int port);
}

View file

@ -20,8 +20,8 @@
#include <QStyleOption>
#include "Config.h"
#include "RaisedButton.h"
#include "WelcomePage.h"
#include "ui/RaisedButton.h"
WelcomePage::WelcomePage(QWidget *parent)
: QWidget(parent)

44
src/WelcomePage.h Normal file
View file

@ -0,0 +1,44 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QWidget>
class RaisedButton;
class WelcomePage : public QWidget
{
Q_OBJECT
public:
explicit WelcomePage(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *event) override;
signals:
// Notify that the user wants to login in.
void userLogin();
// Notify that the user wants to register.
void userRegister();
private:
RaisedButton *registerBtn_;
RaisedButton *loginBtn_;
};

View file

@ -3,14 +3,14 @@
#include <QStyleOption>
#include <QVBoxLayout>
#include "Config.h"
#include "FlatButton.h"
#include "TextField.h"
#include "Theme.h"
#include "ToggleButton.h"
#include "dialogs/CreateRoom.h"
#include "Config.h"
#include "ui/FlatButton.h"
#include "ui/TextField.h"
#include "ui/Theme.h"
#include "ui/ToggleButton.h"
using namespace dialogs;
CreateRoom::CreateRoom(QWidget *parent)

45
src/dialogs/CreateRoom.h Normal file
View file

@ -0,0 +1,45 @@
#pragma once
#include <QFrame>
#include <mtx.hpp>
class FlatButton;
class TextField;
class QComboBox;
class Toggle;
namespace dialogs {
class CreateRoom : public QFrame
{
Q_OBJECT
public:
CreateRoom(QWidget *parent = nullptr);
signals:
void closing(bool isCreating, const mtx::requests::CreateRoom &request);
protected:
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override;
private:
void clearFields();
QComboBox *visibilityCombo_;
QComboBox *presetCombo_;
Toggle *directToggle_;
FlatButton *confirmBtn_;
FlatButton *cancelBtn_;
TextField *nameInput_;
TextField *topicInput_;
TextField *aliasInput_;
mtx::requests::CreateRoom request_;
};
} // dialogs

View file

@ -19,9 +19,10 @@
#include <QDesktopWidget>
#include <QPainter>
#include "Utils.h"
#include "dialogs/ImageOverlay.h"
#include "Utils.h"
using namespace dialogs;
ImageOverlay::ImageOverlay(QPixmap image, QWidget *parent)

View file

@ -0,0 +1,47 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDialog>
#include <QMouseEvent>
#include <QPixmap>
namespace dialogs {
class ImageOverlay : public QWidget
{
Q_OBJECT
public:
ImageOverlay(QPixmap image, QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
signals:
void closing();
private:
QPixmap originalImage_;
QPixmap image_;
QRect content_;
QRect close_button_;
QRect screen_;
};
} // dialogs

View file

@ -6,13 +6,13 @@
#include <QTimer>
#include <QVBoxLayout>
#include "Config.h"
#include "FlatButton.h"
#include "TextField.h"
#include "InviteeItem.h"
#include "dialogs/InviteUsers.h"
#include "Config.h"
#include "InviteeItem.h"
#include "ui/FlatButton.h"
#include "ui/TextField.h"
#include "mtx.hpp"
using namespace dialogs;

42
src/dialogs/InviteUsers.h Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include <QFrame>
#include <QLabel>
#include <QListWidgetItem>
#include <QStringList>
class FlatButton;
class TextField;
class QListWidget;
namespace dialogs {
class InviteUsers : public QFrame
{
Q_OBJECT
public:
explicit InviteUsers(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override;
signals:
void closing(bool isLeaving, QStringList invitees);
private slots:
void removeInvitee(QListWidgetItem *item);
private:
void addUser();
QStringList invitedUsers() const;
FlatButton *confirmBtn_;
FlatButton *cancelBtn_;
TextField *inviteeInput_;
QLabel *errorLabel_;
QListWidget *inviteeList_;
};
} // dialogs

View file

@ -2,13 +2,13 @@
#include <QStyleOption>
#include <QVBoxLayout>
#include "Config.h"
#include "FlatButton.h"
#include "TextField.h"
#include "Theme.h"
#include "dialogs/JoinRoom.h"
#include "Config.h"
#include "ui/FlatButton.h"
#include "ui/TextField.h"
#include "ui/Theme.h"
using namespace dialogs;
JoinRoom::JoinRoom(QWidget *parent)

30
src/dialogs/JoinRoom.h Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include <QFrame>
class FlatButton;
class TextField;
namespace dialogs {
class JoinRoom : public QFrame
{
Q_OBJECT
public:
JoinRoom(QWidget *parent = nullptr);
signals:
void closing(bool isJoining, const QString &room);
protected:
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override;
private:
FlatButton *confirmBtn_;
FlatButton *cancelBtn_;
TextField *roomInput_;
};
} // dialogs

View file

@ -2,12 +2,12 @@
#include <QStyleOption>
#include <QVBoxLayout>
#include "Config.h"
#include "FlatButton.h"
#include "Theme.h"
#include "dialogs/LeaveRoom.h"
#include "Config.h"
#include "ui/FlatButton.h"
#include "ui/Theme.h"
using namespace dialogs;
LeaveRoom::LeaveRoom(QWidget *parent)

25
src/dialogs/LeaveRoom.h Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include <QFrame>
class FlatButton;
namespace dialogs {
class LeaveRoom : public QFrame
{
Q_OBJECT
public:
explicit LeaveRoom(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
signals:
void closing(bool isLeaving);
private:
FlatButton *confirmBtn_;
FlatButton *cancelBtn_;
};
} // dialogs

View file

@ -20,12 +20,12 @@
#include <QStyleOption>
#include <QVBoxLayout>
#include "Config.h"
#include "FlatButton.h"
#include "Theme.h"
#include "dialogs/Logout.h"
#include "Config.h"
#include "ui/FlatButton.h"
#include "ui/Theme.h"
using namespace dialogs;
Logout::Logout(QWidget *parent)

42
src/dialogs/Logout.h Normal file
View file

@ -0,0 +1,42 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QFrame>
class FlatButton;
namespace dialogs {
class Logout : public QFrame
{
Q_OBJECT
public:
explicit Logout(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
signals:
void closing(bool isLoggingOut);
private:
FlatButton *confirmBtn_;
FlatButton *cancelBtn_;
};
} // dialogs

View file

@ -3,15 +3,15 @@
#include <QStyleOption>
#include <QVBoxLayout>
#include "dialogs/MemberList.h"
#include "AvatarProvider.h"
#include "Cache.h"
#include "ChatPage.h"
#include "Config.h"
#include "FlatButton.h"
#include "Utils.h"
#include "Avatar.h"
#include "Cache.h"
#include "dialogs/MemberList.hpp"
#include "ui/Avatar.h"
#include "ui/FlatButton.h"
using namespace dialogs;

61
src/dialogs/MemberList.h Normal file
View file

@ -0,0 +1,61 @@
#pragma once
#include <QFrame>
#include <QListWidget>
class Avatar;
class FlatButton;
class QHBoxLayout;
class QLabel;
class QVBoxLayout;
struct RoomMember;
template<class T>
class QSharedPointer;
namespace dialogs {
class MemberItem : public QWidget
{
Q_OBJECT
public:
MemberItem(const RoomMember &member, QWidget *parent);
private:
QHBoxLayout *topLayout_;
QVBoxLayout *textLayout_;
Avatar *avatar_;
QLabel *userName_;
QLabel *userId_;
};
class MemberList : public QFrame
{
Q_OBJECT
public:
MemberList(const QString &room_id, QWidget *parent = nullptr);
public slots:
void addUsers(const std::vector<RoomMember> &users);
protected:
void paintEvent(QPaintEvent *event) override;
void hideEvent(QHideEvent *event) override
{
list_->clear();
QFrame::hideEvent(event);
}
private:
void moveButtonToBottom();
QString room_id_;
QLabel *topLabel_;
QListWidget *list_;
FlatButton *moreBtn_;
};
} // dialogs

View file

@ -23,13 +23,13 @@
#include <QMimeDatabase>
#include <QVBoxLayout>
#include "dialogs/PreviewUploadOverlay.h"
#include "Config.h"
#include "Logging.hpp"
#include "Logging.h"
#include "MainWindow.h"
#include "Utils.h"
#include "dialogs/PreviewUploadOverlay.h"
using namespace dialogs;
constexpr const char *DEFAULT = "Upload %1?";

View file

@ -0,0 +1,61 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QLabel>
#include <QLineEdit>
#include <QPixmap>
#include <QWidget>
#include "ui/FlatButton.h"
class QMimeData;
namespace dialogs {
class PreviewUploadOverlay : public QWidget
{
Q_OBJECT
public:
PreviewUploadOverlay(QWidget *parent = nullptr);
void setPreview(const QByteArray data, const QString &mime);
void setPreview(const QString &path);
signals:
void confirmUpload(const QByteArray data, const QString &media, const QString &filename);
private:
void init();
void setLabels(const QString &type, const QString &mime, uint64_t upload_size);
bool isImage_;
QPixmap image_;
QByteArray data_;
QString filePath_;
QString mediaType_;
QLabel titleLabel_;
QLabel infoLabel_;
QLineEdit fileName_;
FlatButton upload_;
FlatButton cancel_;
};
} // dialogs

View file

@ -4,13 +4,13 @@
#include <QStyleOption>
#include <QVBoxLayout>
#include "Config.h"
#include "FlatButton.h"
#include "MatrixClient.h"
#include "RaisedButton.h"
#include "Theme.h"
#include "dialogs/ReCaptcha.h"
#include "dialogs/ReCaptcha.hpp"
#include "Config.h"
#include "MatrixClient.h"
#include "ui/FlatButton.h"
#include "ui/RaisedButton.h"
#include "ui/Theme.h"
using namespace dialogs;

28
src/dialogs/ReCaptcha.h Normal file
View file

@ -0,0 +1,28 @@
#pragma once
#include <QWidget>
class FlatButton;
class RaisedButton;
namespace dialogs {
class ReCaptcha : public QWidget
{
Q_OBJECT
public:
ReCaptcha(const QString &session, QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
signals:
void closing();
private:
FlatButton *openCaptchaBtn_;
RaisedButton *confirmBtn_;
RaisedButton *cancelBtn_;
};
} // dialogs

View file

@ -6,14 +6,14 @@
#include <QTimer>
#include <QVBoxLayout>
#include "dialogs/ReadReceipts.h"
#include "AvatarProvider.h"
#include "Cache.h"
#include "ChatPage.h"
#include "Config.h"
#include "Utils.h"
#include "Avatar.h"
#include "AvatarProvider.h"
#include "Cache.h"
#include "dialogs/ReadReceipts.h"
#include "ui/Avatar.h"
using namespace dialogs;

View file

@ -0,0 +1,58 @@
#pragma once
#include <QDateTime>
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QVBoxLayout>
class Avatar;
namespace dialogs {
class ReceiptItem : public QWidget
{
Q_OBJECT
public:
ReceiptItem(QWidget *parent,
const QString &user_id,
uint64_t timestamp,
const QString &room_id);
private:
QString dateFormat(const QDateTime &then) const;
QHBoxLayout *topLayout_;
QVBoxLayout *textLayout_;
Avatar *avatar_;
QLabel *userName_;
QLabel *timestamp_;
};
class ReadReceipts : public QFrame
{
Q_OBJECT
public:
explicit ReadReceipts(QWidget *parent = nullptr);
public slots:
void addUsers(const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &users);
protected:
void paintEvent(QPaintEvent *event) override;
void hideEvent(QHideEvent *event) override
{
userList_->clear();
QFrame::hideEvent(event);
}
private:
QLabel *topLabel_;
QListWidget *userList_;
};
} // dialogs

View file

@ -1,16 +1,3 @@
#include "Avatar.h"
#include "ChatPage.h"
#include "Config.h"
#include "FlatButton.h"
#include "Logging.hpp"
#include "MatrixClient.h"
#include "Painter.h"
#include "TextField.h"
#include "Theme.h"
#include "Utils.h"
#include "dialogs/RoomSettings.hpp"
#include "ui/ToggleButton.h"
#include <QApplication>
#include <QComboBox>
#include <QLabel>
@ -22,6 +9,20 @@
#include <QStyleOption>
#include <QVBoxLayout>
#include "dialogs/RoomSettings.h"
#include "ChatPage.h"
#include "Config.h"
#include "Logging.h"
#include "MatrixClient.h"
#include "Utils.h"
#include "ui/Avatar.h"
#include "ui/FlatButton.h"
#include "ui/Painter.h"
#include "ui/TextField.h"
#include "ui/Theme.h"
#include "ui/ToggleButton.h"
using namespace dialogs;
using namespace mtx::events;

126
src/dialogs/RoomSettings.h Normal file
View file

@ -0,0 +1,126 @@
#pragma once
#include <QFrame>
#include <QImage>
#include "Cache.h"
class Avatar;
class FlatButton;
class QComboBox;
class QHBoxLayout;
class QLabel;
class QLabel;
class QLayout;
class QPixmap;
class TextField;
class TextField;
class Toggle;
template<class T>
class QSharedPointer;
class EditModal : public QWidget
{
Q_OBJECT
public:
EditModal(const QString &roomId, QWidget *parent = nullptr);
void setFields(const QString &roomName, const QString &roomTopic);
signals:
void nameChanged(const QString &roomName);
void nameEventSentCb(const QString &newName);
void topicEventSentCb();
void stateEventErrorCb(const QString &msg);
private:
QString roomId_;
QString initialName_;
QString initialTopic_;
QLabel *errorField_;
TextField *nameInput_;
TextField *topicInput_;
FlatButton *applyBtn_;
FlatButton *cancelBtn_;
};
class TopSection : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
public:
TopSection(const RoomInfo &info, const QImage &img, QWidget *parent = nullptr);
QSize sizeHint() const override;
void setRoomName(const QString &name);
QColor textColor() const { return textColor_; }
void setTextColor(QColor &color) { textColor_ = color; }
protected:
void paintEvent(QPaintEvent *event) override;
private:
static constexpr int AvatarSize = 72;
static constexpr int Padding = 5;
RoomInfo info_;
QPixmap avatar_;
QColor textColor_;
};
namespace dialogs {
class RoomSettings : public QFrame
{
Q_OBJECT
public:
RoomSettings(const QString &room_id, QWidget *parent = nullptr);
signals:
void closing();
void enableEncryptionError(const QString &msg);
protected:
void paintEvent(QPaintEvent *event) override;
private slots:
void saveSettings();
private:
static constexpr int AvatarSize = 64;
void setAvatar(const QImage &img) { avatarImg_ = img; }
void setupEditButton();
//! Retrieve the current room information from cache.
void retrieveRoomInfo();
void enableEncryption();
//! Whether the user would be able to change the name or the topic of the room.
bool hasEditRights_ = true;
bool usesEncryption_ = false;
QHBoxLayout *editLayout_;
// Button section
FlatButton *okBtn_;
FlatButton *cancelBtn_;
FlatButton *editFieldsBtn_;
RoomInfo info_;
QString room_id_;
QImage avatarImg_;
TopSection *topSection_;
QComboBox *accessCombo;
Toggle *encryptionToggle_;
};
} // dialogs

59
src/emoji/Category.h Normal file
View file

@ -0,0 +1,59 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QLabel>
#include <QLayout>
#include <QListView>
#include <QStandardItemModel>
#include "ItemDelegate.h"
namespace emoji {
class Category : public QWidget
{
Q_OBJECT
public:
Category(QString category, std::vector<Emoji> emoji, QWidget *parent = nullptr);
signals:
void emojiSelected(const QString &emoji);
protected:
void paintEvent(QPaintEvent *event) override;
private slots:
void clickIndex(const QModelIndex &index)
{
emit emojiSelected(index.data(Qt::UserRole).toString());
};
private:
QVBoxLayout *mainLayout_;
QStandardItemModel *itemModel_;
QListView *emojiListView_;
emoji::Emoji *data_;
emoji::ItemDelegate *delegate_;
QLabel *category_;
};
} // namespace emoji

43
src/emoji/ItemDelegate.h Normal file
View file

@ -0,0 +1,43 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QModelIndex>
#include <QStandardItemModel>
#include <QStyledItemDelegate>
#include "Provider.h"
namespace emoji {
class ItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit ItemDelegate(QObject *parent = nullptr);
~ItemDelegate();
void paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
private:
Emoji *data_;
};
} // namespace emoji

View file

@ -19,8 +19,8 @@
#include <QScrollBar>
#include <QVBoxLayout>
#include "DropShadow.h"
#include "FlatButton.h"
#include "ui/DropShadow.h"
#include "ui/FlatButton.h"
#include "emoji/Category.h"
#include "emoji/Panel.h"

66
src/emoji/Panel.h Normal file
View file

@ -0,0 +1,66 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QScrollArea>
#include "Provider.h"
namespace emoji {
class Category;
class Panel : public QWidget
{
Q_OBJECT
public:
Panel(QWidget *parent = nullptr);
signals:
void mouseLeft();
void emojiSelected(const QString &emoji);
protected:
void leaveEvent(QEvent *event) override
{
emit leaving();
QWidget::leaveEvent(event);
}
void paintEvent(QPaintEvent *event) override;
signals:
void leaving();
private:
void showCategory(const Category *category);
Provider emoji_provider_;
QScrollArea *scrollArea_;
int shadowMargin_;
// Panel dimensions.
int width_;
int height_;
int categoryIconSize_;
};
} // namespace emoji

53
src/emoji/PickButton.h Normal file
View file

@ -0,0 +1,53 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QEvent>
#include <QTimer>
#include <QWidget>
#include "ui/FlatButton.h"
namespace emoji {
class Panel;
class PickButton : public FlatButton
{
Q_OBJECT
public:
explicit PickButton(QWidget *parent = nullptr);
signals:
void emojiSelected(const QString &emoji);
protected:
void enterEvent(QEvent *e) override;
void leaveEvent(QEvent *e) override;
private:
// Vertical distance from panel's bottom.
int vertical_distance_ = 10;
// Horizontal distance from panel's bottom right corner.
int horizontal_distance_ = 70;
QSharedPointer<Panel> panel_;
QTimer hideTimer_;
};
} // namespace emoji

45
src/emoji/Provider.h Normal file
View file

@ -0,0 +1,45 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include <vector>
namespace emoji {
struct Emoji
{
// Unicode code.
QString unicode;
// Keyboard shortcut e.g :emoji:
QString shortname;
};
class Provider
{
public:
static const std::vector<Emoji> people;
static const std::vector<Emoji> nature;
static const std::vector<Emoji> food;
static const std::vector<Emoji> activity;
static const std::vector<Emoji> travel;
static const std::vector<Emoji> objects;
static const std::vector<Emoji> symbols;
static const std::vector<Emoji> flags;
};
} // namespace emoji

View file

@ -32,12 +32,12 @@
#include <QTranslator>
#include "Config.h"
#include "Logging.hpp"
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "RaisedButton.h"
#include "RunGuard.h"
#include "version.hpp"
#include "ui/RaisedButton.h"
#include "version.h"
#if defined(Q_OS_LINUX)
#include <boost/stacktrace.hpp>

View file

@ -0,0 +1,55 @@
#pragma once
#include <QImage>
#include <QObject>
#include <QString>
#if defined(Q_OS_LINUX)
#include <QtDBus/QDBusArgument>
#include <QtDBus/QDBusInterface>
#endif
struct roomEventId
{
QString roomId;
QString eventId;
};
class NotificationsManager : public QObject
{
Q_OBJECT
public:
NotificationsManager(QObject *parent = nullptr);
void postNotification(const QString &roomId,
const QString &eventId,
const QString &roomName,
const QString &senderName,
const QString &text,
const QImage &icon);
signals:
void notificationClicked(const QString roomId, const QString eventId);
#if defined(Q_OS_LINUX)
private:
QDBusInterface dbus;
uint showNotification(const QString summary, const QString text, const QImage image);
// notification ID to (room ID, event ID)
QMap<uint, roomEventId> notificationIds;
#endif
// 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);
};
#if defined(Q_OS_LINUX)
QDBusArgument &
operator<<(QDBusArgument &arg, const QImage &image);
const QDBusArgument &
operator>>(const QDBusArgument &arg, QImage &);
#endif

View file

@ -20,12 +20,12 @@
#include <QMenu>
#include <QTimer>
#include "Avatar.h"
#include "ChatPage.h"
#include "Config.h"
#include "Logging.hpp"
#include "Olm.hpp"
#include "Painter.h"
#include "Logging.h"
#include "Olm.h"
#include "ui/Avatar.h"
#include "ui/Painter.h"
#include "timeline/TimelineItem.h"
#include "timeline/widgets/AudioItem.h"

380
src/timeline/TimelineItem.h Normal file
View file

@ -0,0 +1,380 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QAbstractTextDocumentLayout>
#include <QApplication>
#include <QDateTime>
#include <QHBoxLayout>
#include <QLabel>
#include <QLayout>
#include <QPainter>
#include <QSettings>
#include <QStyle>
#include <QStyleOption>
#include <QTextBrowser>
#include <QTimer>
#include "AvatarProvider.h"
#include "RoomInfoListItem.h"
#include "Utils.h"
#include "Cache.h"
#include "MatrixClient.h"
class ImageItem;
class StickerItem;
class AudioItem;
class VideoItem;
class FileItem;
class Avatar;
enum class StatusIndicatorState
{
//! The encrypted message was received by the server.
Encrypted,
//! The plaintext message was received by the server.
Received,
//! The client sent the message. Not yet received.
Sent,
//! When the message is loaded from cache or backfill.
Empty,
};
//!
//! Used to notify the user about the status of a message.
//!
class StatusIndicator : public QWidget
{
Q_OBJECT
public:
explicit StatusIndicator(QWidget *parent);
void setState(StatusIndicatorState state);
protected:
void paintEvent(QPaintEvent *event) override;
private:
void paintIcon(QPainter &p, QIcon &icon);
QIcon lockIcon_;
QIcon clockIcon_;
QIcon checkmarkIcon_;
QColor iconColor_ = QColor("#999");
StatusIndicatorState state_ = StatusIndicatorState::Empty;
static constexpr int MaxWidth = 24;
};
class TextLabel : public QTextBrowser
{
Q_OBJECT
public:
TextLabel(const QString &text, QWidget *parent = 0)
: QTextBrowser(parent)
{
setText(text);
setOpenExternalLinks(true);
// Make it look and feel like an ordinary label.
setReadOnly(true);
setFrameStyle(QFrame::NoFrame);
QPalette pal = palette();
pal.setColor(QPalette::Base, Qt::transparent);
setPalette(pal);
// Wrap anywhere but prefer words, adjust minimum height on the fly.
setLineWrapMode(QTextEdit::WidgetWidth);
setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
connect(document()->documentLayout(),
&QAbstractTextDocumentLayout::documentSizeChanged,
this,
&TextLabel::adjustHeight);
document()->setDocumentMargin(0);
setFixedHeight(20);
}
void wheelEvent(QWheelEvent *event) override { event->ignore(); }
private slots:
void adjustHeight(const QSizeF &size) { setFixedHeight(size.height()); }
};
class UserProfileFilter : public QObject
{
Q_OBJECT
public:
explicit UserProfileFilter(const QString &user_id, QLabel *parent)
: QObject(parent)
, user_id_{user_id}
{}
signals:
void hoverOff();
void hoverOn();
protected:
bool eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseButtonRelease) {
// QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
// TODO: Open user profile
return true;
} else if (event->type() == QEvent::HoverLeave) {
emit hoverOff();
return true;
} else if (event->type() == QEvent::HoverEnter) {
emit hoverOn();
return true;
}
return QObject::eventFilter(obj, event);
}
private:
QString user_id_;
};
class TimelineItem : public QWidget
{
Q_OBJECT
public:
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &e,
bool with_sender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> &e,
bool with_sender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> &e,
bool with_sender,
const QString &room_id,
QWidget *parent = 0);
// For local messages.
// m.text & m.emote
TimelineItem(mtx::events::MessageType ty,
const QString &userid,
QString body,
bool withSender,
const QString &room_id,
QWidget *parent = 0);
// m.image
TimelineItem(ImageItem *item,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(FileItem *item,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(AudioItem *item,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(VideoItem *item,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(ImageItem *img,
const mtx::events::RoomEvent<mtx::events::msg::Image> &e,
bool with_sender,
const QString &room_id,
QWidget *parent);
TimelineItem(StickerItem *img,
const mtx::events::Sticker &e,
bool with_sender,
const QString &room_id,
QWidget *parent);
TimelineItem(FileItem *file,
const mtx::events::RoomEvent<mtx::events::msg::File> &e,
bool with_sender,
const QString &room_id,
QWidget *parent);
TimelineItem(AudioItem *audio,
const mtx::events::RoomEvent<mtx::events::msg::Audio> &e,
bool with_sender,
const QString &room_id,
QWidget *parent);
TimelineItem(VideoItem *video,
const mtx::events::RoomEvent<mtx::events::msg::Video> &e,
bool with_sender,
const QString &room_id,
QWidget *parent);
void setUserAvatar(const QImage &pixmap);
DescInfo descriptionMessage() const { return descriptionMsg_; }
QString eventId() const { return event_id_; }
void setEventId(const QString &event_id) { event_id_ = event_id; }
void markReceived(bool isEncrypted);
void markSent();
bool isReceived() { return isReceived_; };
void setRoomId(QString room_id) { room_id_ = room_id; }
void sendReadReceipt() const;
//! Add a user avatar for this event.
void addAvatar();
void addKeyRequestAction();
signals:
void eventRedacted(const QString &event_id);
void redactionFailed(const QString &msg);
protected:
void paintEvent(QPaintEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
private:
void init();
//! Add a context menu option to save the image of the timeline item.
void addSaveImageAction(ImageItem *image);
//! Add the reply action in the context menu for widgets that support it.
void addReplyAction();
template<class Widget>
void setupLocalWidgetLayout(Widget *widget, const QString &userid, bool withSender);
template<class Event, class Widget>
void setupWidgetLayout(Widget *widget, const Event &event, bool withSender);
void generateBody(const QString &body);
void generateBody(const QString &user_id, const QString &displayname, const QString &body);
void generateTimestamp(const QDateTime &time);
void setupAvatarLayout(const QString &userName);
void setupSimpleLayout();
void adjustMessageLayout();
void adjustMessageLayoutForWidget();
//! Whether or not the event associated with the widget
//! has been acknowledged by the server.
bool isReceived_ = false;
QString replaceEmoji(const QString &body);
QString event_id_;
QString room_id_;
DescInfo descriptionMsg_;
QMenu *contextMenu_;
QAction *showReadReceipts_;
QAction *markAsRead_;
QAction *redactMsg_;
QAction *replyMsg_;
QHBoxLayout *topLayout_ = nullptr;
QHBoxLayout *messageLayout_ = nullptr;
QVBoxLayout *mainLayout_ = nullptr;
QHBoxLayout *widgetLayout_ = nullptr;
Avatar *userAvatar_;
QFont font_;
QFont usernameFont_;
StatusIndicator *statusIndicator_;
QLabel *timestamp_;
QLabel *userName_;
TextLabel *body_;
};
template<class Widget>
void
TimelineItem::setupLocalWidgetLayout(Widget *widget, const QString &userid, bool withSender)
{
auto displayName = Cache::displayName(room_id_, userid);
auto timestamp = QDateTime::currentDateTime();
descriptionMsg_ = {"You",
userid,
QString(" %1").arg(utils::messageDescription<Widget>()),
utils::descriptiveTime(timestamp),
timestamp};
generateTimestamp(timestamp);
widgetLayout_ = new QHBoxLayout;
widgetLayout_->setContentsMargins(0, 2, 0, 2);
widgetLayout_->addWidget(widget);
widgetLayout_->addStretch(1);
if (withSender) {
generateBody(userid, displayName, "");
setupAvatarLayout(displayName);
AvatarProvider::resolve(
room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
setupSimpleLayout();
}
adjustMessageLayoutForWidget();
}
template<class Event, class Widget>
void
TimelineItem::setupWidgetLayout(Widget *widget, const Event &event, bool withSender)
{
init();
event_id_ = QString::fromStdString(event.event_id);
const auto sender = QString::fromStdString(event.sender);
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
auto displayName = Cache::displayName(room_id_, sender);
QSettings settings;
descriptionMsg_ = {sender == settings.value("auth/user_id") ? "You" : displayName,
sender,
QString(" %1").arg(utils::messageDescription<Widget>()),
utils::descriptiveTime(timestamp),
timestamp};
generateTimestamp(timestamp);
widgetLayout_ = new QHBoxLayout();
widgetLayout_->setContentsMargins(0, 2, 0, 2);
widgetLayout_->addWidget(widget);
widgetLayout_->addStretch(1);
if (withSender) {
generateBody(sender, displayName, "");
setupAvatarLayout(displayName);
AvatarProvider::resolve(
room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
setupSimpleLayout();
}
adjustMessageLayoutForWidget();
}

View file

@ -22,12 +22,12 @@
#include "Cache.h"
#include "ChatPage.h"
#include "Config.h"
#include "FloatingButton.h"
#include "InfoMessage.hpp"
#include "Logging.hpp"
#include "Olm.hpp"
#include "Logging.h"
#include "Olm.h"
#include "UserSettingsPage.h"
#include "Utils.h"
#include "ui/FloatingButton.h"
#include "ui/InfoMessage.h"
#include "timeline/TimelineView.h"
#include "timeline/widgets/AudioItem.h"

426
src/timeline/TimelineView.h Normal file
View file

@ -0,0 +1,426 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QApplication>
#include <QLayout>
#include <QList>
#include <QQueue>
#include <QScrollArea>
#include <QStyle>
#include <QStyleOption>
#include <QTimer>
#include <mtx/events.hpp>
#include <mtx/responses/messages.hpp>
#include "MatrixClient.h"
#include "timeline/TimelineItem.h"
#include "ui/ScrollBar.h"
class StateKeeper
{
public:
StateKeeper(std::function<void()> &&fn)
: fn_(std::move(fn))
{}
~StateKeeper() { fn_(); }
private:
std::function<void()> fn_;
};
struct DecryptionResult
{
//! The decrypted content as a normal plaintext event.
utils::TimelineEvent event;
//! Whether or not the decryption was successful.
bool isDecrypted = false;
};
class FloatingButton;
struct DescInfo;
// Contains info about a message shown in the history view
// but not yet confirmed by the homeserver through sync.
struct PendingMessage
{
mtx::events::MessageType ty;
std::string txn_id;
QString body;
QString filename;
QString mime;
uint64_t media_size;
QString event_id;
TimelineItem *widget;
QSize dimensions;
bool is_encrypted = false;
};
template<class MessageT>
MessageT
toRoomMessage(const PendingMessage &) = delete;
template<>
mtx::events::msg::Audio
toRoomMessage<mtx::events::msg::Audio>(const PendingMessage &m);
template<>
mtx::events::msg::Emote
toRoomMessage<mtx::events::msg::Emote>(const PendingMessage &m);
template<>
mtx::events::msg::File
toRoomMessage<mtx::events::msg::File>(const PendingMessage &);
template<>
mtx::events::msg::Image
toRoomMessage<mtx::events::msg::Image>(const PendingMessage &m);
template<>
mtx::events::msg::Text
toRoomMessage<mtx::events::msg::Text>(const PendingMessage &);
template<>
mtx::events::msg::Video
toRoomMessage<mtx::events::msg::Video>(const PendingMessage &m);
// In which place new TimelineItems should be inserted.
enum class TimelineDirection
{
Top,
Bottom,
};
class TimelineView : public QWidget
{
Q_OBJECT
public:
TimelineView(const mtx::responses::Timeline &timeline,
const QString &room_id,
QWidget *parent = 0);
TimelineView(const QString &room_id, QWidget *parent = 0);
// Add new events at the end of the timeline.
void addEvents(const mtx::responses::Timeline &timeline);
void addUserMessage(mtx::events::MessageType ty, const QString &msg);
template<class Widget, mtx::events::MessageType MsgType>
void addUserMessage(const QString &url,
const QString &filename,
const QString &mime,
uint64_t size,
const QSize &dimensions = QSize());
void updatePendingMessage(const std::string &txn_id, const QString &event_id);
void scrollDown();
//! Remove an item from the timeline with the given Event ID.
void removeEvent(const QString &event_id);
void setPrevBatchToken(const QString &token) { prev_batch_token_ = token; }
public slots:
void sliderRangeChanged(int min, int max);
void sliderMoved(int position);
void fetchHistory();
// Add old events at the top of the timeline.
void addBackwardsEvents(const mtx::responses::Messages &msgs);
// Whether or not the initial batch has been loaded.
bool hasLoaded() { return scroll_layout_->count() > 1 || isTimelineFinished; }
void handleFailedMessage(const std::string &txn_id);
private slots:
void sendNextPendingMessage();
signals:
void updateLastTimelineMessage(const QString &user, const DescInfo &info);
void messagesRetrieved(const mtx::responses::Messages &res);
void messageFailed(const std::string &txn_id);
void messageSent(const std::string &txn_id, const QString &event_id);
protected:
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override;
bool event(QEvent *event) override;
private:
using TimelineEvent = mtx::events::collections::TimelineEvents;
QWidget *relativeWidget(QWidget *item, int dt) const;
DecryptionResult parseEncryptedEvent(
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
void handleClaimedKeys(std::shared_ptr<StateKeeper> keeper,
const std::map<std::string, std::string> &room_key,
const std::map<std::string, DevicePublicKeys> &pks,
const std::string &user_id,
const mtx::responses::ClaimKeys &res,
mtx::http::RequestErr err);
//! Callback for all message sending.
void sendRoomMessageHandler(const std::string &txn_id,
const mtx::responses::EventId &res,
mtx::http::RequestErr err);
void prepareEncryptedMessage(const PendingMessage &msg);
//! Call the /messages endpoint to fill the timeline.
void getMessages();
//! HACK: Fixing layout flickering when adding to the bottom
//! of the timeline.
void pushTimelineItem(QWidget *item)
{
item->hide();
scroll_layout_->addWidget(item);
QTimer::singleShot(0, this, [item]() { item->show(); });
};
//! Decides whether or not to show or hide the scroll down button.
void toggleScrollDownButton();
void init();
void addTimelineItem(QWidget *item,
TimelineDirection direction = TimelineDirection::Bottom);
void updateLastSender(const QString &user_id, TimelineDirection direction);
void notifyForLastEvent();
void notifyForLastEvent(const TimelineEvent &event);
//! Keep track of the sender and the timestamp of the current message.
void saveLastMessageInfo(const QString &sender, const QDateTime &datetime)
{
lastSender_ = sender;
lastMsgTimestamp_ = datetime;
}
void saveFirstMessageInfo(const QString &sender, const QDateTime &datetime)
{
firstSender_ = sender;
firstMsgTimestamp_ = datetime;
}
//! Keep track of the sender and the timestamp of the current message.
void saveMessageInfo(const QString &sender,
uint64_t origin_server_ts,
TimelineDirection direction);
TimelineEvent findFirstViewableEvent(const std::vector<TimelineEvent> &events);
TimelineEvent findLastViewableEvent(const std::vector<TimelineEvent> &events);
//! Mark the last event as read.
void readLastEvent() const;
//! Whether or not the scrollbar is visible (non-zero height).
bool isScrollbarActivated() { return scroll_area_->verticalScrollBar()->value() != 0; }
//! Retrieve the event id of the last item.
QString getLastEventId() const;
template<class Event, class Widget>
TimelineItem *processMessageEvent(const Event &event, TimelineDirection direction);
// TODO: Remove this eventually.
template<class Event>
TimelineItem *processMessageEvent(const Event &event, TimelineDirection direction);
// For events with custom display widgets.
template<class Event, class Widget>
TimelineItem *createTimelineItem(const Event &event, bool withSender);
// For events without custom display widgets.
// TODO: All events should have custom widgets.
template<class Event>
TimelineItem *createTimelineItem(const Event &event, bool withSender);
// Used to determine whether or not we should prefix a message with the
// sender's name.
bool isSenderRendered(const QString &user_id,
uint64_t origin_server_ts,
TimelineDirection direction);
bool isPendingMessage(const std::string &txn_id,
const QString &sender,
const QString &userid);
void removePendingMessage(const std::string &txn_id);
bool isDuplicate(const QString &event_id) { return eventIds_.contains(event_id); }
void handleNewUserMessage(PendingMessage msg);
bool isDateDifference(const QDateTime &first,
const QDateTime &second = QDateTime::currentDateTime()) const;
// Return nullptr if the event couldn't be parsed.
QWidget *parseMessageEvent(const mtx::events::collections::TimelineEvents &event,
TimelineDirection direction);
//! Store the event id associated with the given widget.
void saveEventId(QWidget *widget);
QVBoxLayout *top_layout_;
QVBoxLayout *scroll_layout_;
QScrollArea *scroll_area_;
ScrollBar *scrollbar_;
QWidget *scroll_widget_;
QString firstSender_;
QDateTime firstMsgTimestamp_;
QString lastSender_;
QDateTime lastMsgTimestamp_;
QString room_id_;
QString prev_batch_token_;
QString local_user_;
bool isPaginationInProgress_ = false;
// Keeps track whether or not the user has visited the view.
bool isInitialized = false;
bool isTimelineFinished = false;
bool isInitialSync = true;
const int SCROLL_BAR_GAP = 200;
QTimer *paginationTimer_;
int scroll_height_ = 0;
int previous_max_height_ = 0;
int oldPosition_;
int oldHeight_;
FloatingButton *scrollDownBtn_;
TimelineDirection lastMessageDirection_;
//! Messages received by sync not added to the timeline.
std::vector<TimelineEvent> bottomMessages_;
//! Messages received by /messages not added to the timeline.
std::vector<TimelineEvent> topMessages_;
//! Render the given timeline events to the bottom of the timeline.
void renderBottomEvents(const std::vector<TimelineEvent> &events);
//! Render the given timeline events to the top of the timeline.
void renderTopEvents(const std::vector<TimelineEvent> &events);
// The events currently rendered. Used for duplicate detection.
QMap<QString, QWidget *> eventIds_;
QQueue<PendingMessage> pending_msgs_;
QList<PendingMessage> pending_sent_msgs_;
};
template<class Widget, mtx::events::MessageType MsgType>
void
TimelineView::addUserMessage(const QString &url,
const QString &filename,
const QString &mime,
uint64_t size,
const QSize &dimensions)
{
auto with_sender = (lastSender_ != local_user_) || isDateDifference(lastMsgTimestamp_);
auto trimmed = QFileInfo{filename}.fileName(); // Trim file path.
auto widget = new Widget(url, trimmed, size, this);
TimelineItem *view_item =
new TimelineItem(widget, local_user_, with_sender, room_id_, scroll_widget_);
addTimelineItem(view_item);
lastMessageDirection_ = TimelineDirection::Bottom;
// Keep track of the sender and the timestamp of the current message.
saveLastMessageInfo(local_user_, QDateTime::currentDateTime());
PendingMessage message;
message.ty = MsgType;
message.txn_id = http::client()->generate_txn_id();
message.body = url;
message.filename = trimmed;
message.mime = mime;
message.media_size = size;
message.widget = view_item;
message.dimensions = dimensions;
handleNewUserMessage(message);
}
template<class Event>
TimelineItem *
TimelineView::createTimelineItem(const Event &event, bool withSender)
{
TimelineItem *item = new TimelineItem(event, withSender, room_id_, scroll_widget_);
return item;
}
template<class Event, class Widget>
TimelineItem *
TimelineView::createTimelineItem(const Event &event, bool withSender)
{
auto eventWidget = new Widget(event);
auto item = new TimelineItem(eventWidget, event, withSender, room_id_, scroll_widget_);
return item;
}
template<class Event>
TimelineItem *
TimelineView::processMessageEvent(const Event &event, TimelineDirection direction)
{
const auto event_id = QString::fromStdString(event.event_id);
const auto sender = QString::fromStdString(event.sender);
const auto txn_id = event.unsigned_data.transaction_id;
if ((!txn_id.empty() && isPendingMessage(txn_id, sender, local_user_)) ||
isDuplicate(event_id)) {
removePendingMessage(txn_id);
return nullptr;
}
auto with_sender = isSenderRendered(sender, event.origin_server_ts, direction);
saveMessageInfo(sender, event.origin_server_ts, direction);
auto item = createTimelineItem<Event>(event, with_sender);
eventIds_[event_id] = item;
return item;
}
template<class Event, class Widget>
TimelineItem *
TimelineView::processMessageEvent(const Event &event, TimelineDirection direction)
{
const auto event_id = QString::fromStdString(event.event_id);
const auto sender = QString::fromStdString(event.sender);
const auto txn_id = event.unsigned_data.transaction_id;
if ((!txn_id.empty() && isPendingMessage(txn_id, sender, local_user_)) ||
isDuplicate(event_id)) {
removePendingMessage(txn_id);
return nullptr;
}
auto with_sender = isSenderRendered(sender, event.origin_server_ts, direction);
saveMessageInfo(sender, event.origin_server_ts, direction);
auto item = createTimelineItem<Event, Widget>(event, with_sender);
eventIds_[event_id] = item;
return item;
}

View file

@ -22,7 +22,7 @@
#include <QSettings>
#include "Cache.h"
#include "Logging.hpp"
#include "Logging.h"
#include "timeline/TimelineView.h"
#include "timeline/TimelineViewManager.h"
#include "timeline/widgets/AudioItem.h"

View file

@ -0,0 +1,94 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QSharedPointer>
#include <QStackedWidget>
#include <mtx.hpp>
class QFile;
class RoomInfoListItem;
class TimelineView;
struct DescInfo;
struct SavedMessages;
class TimelineViewManager : public QStackedWidget
{
Q_OBJECT
public:
TimelineViewManager(QWidget *parent);
// Initialize with timeline events.
void initialize(const mtx::responses::Rooms &rooms);
// Empty initialization.
void initialize(const std::vector<std::string> &rooms);
void addRoom(const mtx::responses::JoinedRoom &room, const QString &room_id);
void addRoom(const QString &room_id);
void sync(const mtx::responses::Rooms &rooms);
void clearAll() { views_.clear(); }
// Check if all the timelines have been loaded.
bool hasLoaded() const;
static QString chooseRandomColor();
signals:
void clearRoomMessageCount(QString roomid);
void updateRoomsLastMessage(const QString &user, const DescInfo &info);
public slots:
void removeTimelineEvent(const QString &room_id, const QString &event_id);
void initWithMessages(const std::map<QString, mtx::responses::Timeline> &msgs);
void setHistoryView(const QString &room_id);
void queueTextMessage(const QString &msg);
void queueEmoteMessage(const QString &msg);
void queueImageMessage(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
uint64_t dsize,
const QSize &dimensions);
void queueFileMessage(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
uint64_t dsize);
void queueAudioMessage(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
uint64_t dsize);
void queueVideoMessage(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
uint64_t dsize);
private:
//! Check if the given room id is managed by a TimelineView.
bool timelineViewExists(const QString &id) { return views_.find(id) != views_.end(); }
QString active_room_;
std::map<QString, QSharedPointer<TimelineView>> views_;
};

View file

@ -22,7 +22,7 @@
#include <QPainter>
#include <QPixmap>
#include "Logging.hpp"
#include "Logging.h"
#include "MatrixClient.h"
#include "Utils.h"

View file

@ -0,0 +1,107 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QEvent>
#include <QIcon>
#include <QMediaPlayer>
#include <QMouseEvent>
#include <QSharedPointer>
#include <QWidget>
#include <mtx.hpp>
class AudioItem : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
Q_PROPERTY(QColor iconColor WRITE setIconColor READ iconColor)
Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
Q_PROPERTY(QColor durationBackgroundColor WRITE setDurationBackgroundColor READ
durationBackgroundColor)
Q_PROPERTY(QColor durationForegroundColor WRITE setDurationForegroundColor READ
durationForegroundColor)
public:
AudioItem(const mtx::events::RoomEvent<mtx::events::msg::Audio> &event,
QWidget *parent = nullptr);
AudioItem(const QString &url,
const QString &filename,
uint64_t size,
QWidget *parent = nullptr);
QSize sizeHint() const override;
void setTextColor(const QColor &color) { textColor_ = color; }
void setIconColor(const QColor &color) { iconColor_ = color; }
void setBackgroundColor(const QColor &color) { backgroundColor_ = color; }
void setDurationBackgroundColor(const QColor &color) { durationBgColor_ = color; }
void setDurationForegroundColor(const QColor &color) { durationFgColor_ = color; }
QColor textColor() const { return textColor_; }
QColor iconColor() const { return iconColor_; }
QColor backgroundColor() const { return backgroundColor_; }
QColor durationBackgroundColor() const { return durationBgColor_; }
QColor durationForegroundColor() const { return durationFgColor_; }
protected:
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
signals:
void fileDownloadedCb(const QByteArray &data);
private slots:
void fileDownloaded(const QByteArray &data);
private:
void init();
enum class AudioState
{
Play,
Pause,
};
AudioState state_ = AudioState::Play;
QUrl url_;
QString text_;
QString readableFileSize_;
QString filenameToSave_;
mtx::events::RoomEvent<mtx::events::msg::Audio> event_;
QMediaPlayer *player_;
QIcon playIcon_;
QIcon pauseIcon_;
QColor textColor_ = QColor("white");
QColor iconColor_ = QColor("#38A3D8");
QColor backgroundColor_ = QColor("#333");
QColor durationBgColor_ = QColor("black");
QColor durationFgColor_ = QColor("blue");
};

View file

@ -22,7 +22,7 @@
#include <QPainter>
#include <QPixmap>
#include "Logging.hpp"
#include "Logging.h"
#include "MatrixClient.h"
#include "Utils.h"

View file

@ -0,0 +1,82 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QEvent>
#include <QIcon>
#include <QMouseEvent>
#include <QSharedPointer>
#include <QWidget>
#include <mtx.hpp>
class FileItem : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
Q_PROPERTY(QColor iconColor WRITE setIconColor READ iconColor)
Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
public:
FileItem(const mtx::events::RoomEvent<mtx::events::msg::File> &event,
QWidget *parent = nullptr);
FileItem(const QString &url,
const QString &filename,
uint64_t size,
QWidget *parent = nullptr);
QSize sizeHint() const override;
void setTextColor(const QColor &color) { textColor_ = color; }
void setIconColor(const QColor &color) { iconColor_ = color; }
void setBackgroundColor(const QColor &color) { backgroundColor_ = color; }
QColor textColor() const { return textColor_; }
QColor iconColor() const { return iconColor_; }
QColor backgroundColor() const { return backgroundColor_; }
signals:
void fileDownloadedCb(const QByteArray &data);
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private slots:
void fileDownloaded(const QByteArray &data);
private:
void openUrl();
void init();
QUrl url_;
QString text_;
QString readableFileSize_;
QString filenameToSave_;
mtx::events::RoomEvent<mtx::events::msg::File> event_;
QIcon icon_;
QColor textColor_ = QColor("white");
QColor iconColor_ = QColor("#38A3D8");
QColor backgroundColor_ = QColor("#333");
};

View file

@ -24,11 +24,11 @@
#include <QUuid>
#include "Config.h"
#include "Logging.hpp"
#include "ImageItem.h"
#include "Logging.h"
#include "MatrixClient.h"
#include "Utils.h"
#include "dialogs/ImageOverlay.h"
#include "timeline/widgets/ImageItem.h"
void
ImageItem::downloadMedia(const QUrl &url)

Some files were not shown because too many files have changed in this diff Show more