diff --git a/src/AvatarProvider.cc b/src/AvatarProvider.cpp
index b4c1188a..dbfc1945 100644
--- a/src/AvatarProvider.cc
+++ b/src/AvatarProvider.cpp
@@ -20,7 +20,7 @@
#include "AvatarProvider.h"
#include "Cache.h"
-#include "Logging.hpp"
+#include "Logging.h"
#include "MatrixClient.h"
namespace AvatarProvider {
diff --git a/src/AvatarProvider.h b/src/AvatarProvider.h
new file mode 100644
index 00000000..4b4e15e9
--- /dev/null
+++ b/src/AvatarProvider.h
@@ -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);
+}
diff --git a/src/Cache.cc b/src/Cache.cpp
index 614e8a90..6f71b746 100644
--- a/src/Cache.cc
+++ b/src/Cache.cpp
@@ -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.
diff --git a/src/Cache.h b/src/Cache.h
new file mode 100644
index 00000000..fa8355a5
--- /dev/null
+++ b/src/Cache.h
@@ -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();
+}
diff --git a/src/ChatPage.cc b/src/ChatPage.cpp
index ff059cee..cc7a5741 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cpp
@@ -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"
diff --git a/src/ChatPage.h b/src/ChatPage.h
new file mode 100644
index 00000000..6a70acf4
--- /dev/null
+++ b/src/ChatPage.h
@@ -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;
+}
diff --git a/src/CommunitiesList.cc b/src/CommunitiesList.cpp
index 822ca1d2..c271be89 100644
--- a/src/CommunitiesList.cc
+++ b/src/CommunitiesList.cpp
@@ -1,6 +1,6 @@
#include "CommunitiesList.h"
#include "Cache.h"
-#include "Logging.hpp"
+#include "Logging.h"
#include "MatrixClient.h"
#include <QLabel>
diff --git a/src/CommunitiesList.h b/src/CommunitiesList.h
new file mode 100644
index 00000000..32a64bf2
--- /dev/null
+++ b/src/CommunitiesList.h
@@ -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_;
+};
diff --git a/src/CommunitiesListItem.cc b/src/CommunitiesListItem.cpp
index df6c5393..8afaebff 100644
--- a/src/CommunitiesListItem.cc
+++ b/src/CommunitiesListItem.cpp
@@ -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)
diff --git a/src/CommunitiesListItem.h b/src/CommunitiesListItem.h
new file mode 100644
index 00000000..a9b6e333
--- /dev/null
+++ b/src/CommunitiesListItem.h
@@ -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_;
+};
diff --git a/src/Config.h b/src/Config.h
new file mode 100644
index 00000000..3a3296d6
--- /dev/null
+++ b/src/Config.h
@@ -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
diff --git a/src/InviteeItem.cc b/src/InviteeItem.cpp
index 5ae2a7b6..6e9be0d5 100644
--- a/src/InviteeItem.cc
+++ b/src/InviteeItem.cpp
@@ -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;
diff --git a/src/InviteeItem.h b/src/InviteeItem.h
new file mode 100644
index 00000000..f0bdbdf0
--- /dev/null
+++ b/src/InviteeItem.h
@@ -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_;
+};
diff --git a/src/Logging.cpp b/src/Logging.cpp
index bccbe389..1b2838f3 100644
--- a/src/Logging.cpp
+++ b/src/Logging.cpp
@@ -1,4 +1,4 @@
-#include "Logging.hpp"
+#include "Logging.h"
#include <iostream>
#include <spdlog/sinks/file_sinks.h>
diff --git a/src/Logging.h b/src/Logging.h
new file mode 100644
index 00000000..2feae60d
--- /dev/null
+++ b/src/Logging.h
@@ -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();
+}
diff --git a/src/LoginPage.cc b/src/LoginPage.cpp
index 6a3b925c..dbf9d470 100644
--- a/src/LoginPage.cc
+++ b/src/LoginPage.cpp
@@ -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;
diff --git a/src/LoginPage.h b/src/LoginPage.h
new file mode 100644
index 00000000..c52ccaa4
--- /dev/null
+++ b/src/LoginPage.h
@@ -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_;
+};
diff --git a/src/MainWindow.cc b/src/MainWindow.cpp
index 749e7caf..fdca98c3 100644
--- a/src/MainWindow.cc
+++ b/src/MainWindow.cpp
@@ -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;
diff --git a/src/MainWindow.h b/src/MainWindow.h
new file mode 100644
index 00000000..92040191
--- /dev/null
+++ b/src/MainWindow.h
@@ -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_;
+};
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cpp
index e41c66c1..e41c66c1 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cpp
diff --git a/src/MatrixClient.h b/src/MatrixClient.h
new file mode 100644
index 00000000..12bba889
--- /dev/null
+++ b/src/MatrixClient.h
@@ -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();
+}
diff --git a/src/Olm.cpp b/src/Olm.cpp
index b3bb4316..fe4265d7 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -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;
diff --git a/src/Olm.h b/src/Olm.h
new file mode 100644
index 00000000..ae4e0659
--- /dev/null
+++ b/src/Olm.h
@@ -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
diff --git a/src/QuickSwitcher.cc b/src/QuickSwitcher.cpp
index 3c9725d1..07460efb 100644
--- a/src/QuickSwitcher.cc
+++ b/src/QuickSwitcher.cpp
@@ -23,6 +23,7 @@
#include <QtConcurrent>
#include "QuickSwitcher.h"
+#include "SuggestionsPopup.h"
RoomSearchInput::RoomSearchInput(QWidget *parent)
: TextField(parent)
diff --git a/src/QuickSwitcher.h b/src/QuickSwitcher.h
new file mode 100644
index 00000000..24b9adfa
--- /dev/null
+++ b/src/QuickSwitcher.h
@@ -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_;
+};
diff --git a/src/RegisterPage.cc b/src/RegisterPage.cpp
index 4894d122..5a02713a 100644
--- a/src/RegisterPage.cc
+++ b/src/RegisterPage.cpp
@@ -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)
diff --git a/src/RegisterPage.h b/src/RegisterPage.h
new file mode 100644
index 00000000..d02de7c4
--- /dev/null
+++ b/src/RegisterPage.h
@@ -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_;
+};
diff --git a/src/RoomInfoListItem.cc b/src/RoomInfoListItem.cpp
index 7027115f..172cdb90 100644
--- a/src/RoomInfoListItem.cc
+++ b/src/RoomInfoListItem.cpp
@@ -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;
diff --git a/src/RoomInfoListItem.h b/src/RoomInfoListItem.h
new file mode 100644
index 00000000..95db1d75
--- /dev/null
+++ b/src/RoomInfoListItem.h
@@ -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_;
+};
diff --git a/src/RoomList.cc b/src/RoomList.cpp
index 418a5d6f..a9328984 100644
--- a/src/RoomList.cc
+++ b/src/RoomList.cpp
@@ -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)
diff --git a/src/RoomList.h b/src/RoomList.h
new file mode 100644
index 00000000..59b0e865
--- /dev/null
+++ b/src/RoomList.h
@@ -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;
+};
diff --git a/src/RunGuard.cc b/src/RunGuard.cpp
index 75833eb7..75833eb7 100644
--- a/src/RunGuard.cc
+++ b/src/RunGuard.cpp
diff --git a/src/RunGuard.h b/src/RunGuard.h
new file mode 100644
index 00000000..f9a9641a
--- /dev/null
+++ b/src/RunGuard.h
@@ -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)
+};
diff --git a/src/SideBarActions.cc b/src/SideBarActions.cpp
index d65900b3..b2a01e3e 100644
--- a/src/SideBarActions.cc
+++ b/src/SideBarActions.cpp
@@ -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}
diff --git a/src/SideBarActions.h b/src/SideBarActions.h
new file mode 100644
index 00000000..f97c72de
--- /dev/null
+++ b/src/SideBarActions.h
@@ -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_;
+};
diff --git a/src/Splitter.cc b/src/Splitter.cpp
index 7b6c9573..f5bbf367 100644
--- a/src/Splitter.cc
+++ b/src/Splitter.cpp
@@ -23,7 +23,7 @@
#include "Config.h"
#include "Splitter.h"
-#include "Theme.h"
+#include "ui/Theme.h"
constexpr auto MaxWidth = (1 << 24) - 1;
diff --git a/src/Splitter.h b/src/Splitter.h
new file mode 100644
index 00000000..99e02eed
--- /dev/null
+++ b/src/Splitter.h
@@ -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;
+};
diff --git a/src/SuggestionsPopup.cpp b/src/SuggestionsPopup.cpp
index bcfcb233..5ea78460 100644
--- a/src/SuggestionsPopup.cpp
+++ b/src/SuggestionsPopup.cpp
@@ -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;
diff --git a/src/SuggestionsPopup.h b/src/SuggestionsPopup.h
new file mode 100644
index 00000000..72d6c7eb
--- /dev/null
+++ b/src/SuggestionsPopup.h
@@ -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;
+};
diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cpp
index bb72c533..a419ed84 100644
--- a/src/TextInputWidget.cc
+++ b/src/TextInputWidget.cpp
@@ -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;
diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h
new file mode 100644
index 00000000..e7d5f948
--- /dev/null
+++ b/src/TextInputWidget.h
@@ -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_;
+};
diff --git a/src/TopRoomBar.cc b/src/TopRoomBar.cpp
index 7b2814b9..c9609788 100644
--- a/src/TopRoomBar.cc
+++ b/src/TopRoomBar.cpp
@@ -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)
diff --git a/src/TopRoomBar.h b/src/TopRoomBar.h
new file mode 100644
index 00000000..1c42e25f
--- /dev/null
+++ b/src/TopRoomBar.h
@@ -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_;
+};
diff --git a/src/TrayIcon.cc b/src/TrayIcon.cpp
index ac84aaca..ac84aaca 100644
--- a/src/TrayIcon.cc
+++ b/src/TrayIcon.cpp
diff --git a/src/TrayIcon.h b/src/TrayIcon.h
new file mode 100644
index 00000000..a3536cc3
--- /dev/null
+++ b/src/TrayIcon.h
@@ -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_;
+};
diff --git a/src/TypingDisplay.cc b/src/TypingDisplay.cpp
index da9c1679..da9c1679 100644
--- a/src/TypingDisplay.cc
+++ b/src/TypingDisplay.cpp
diff --git a/src/TypingDisplay.h b/src/TypingDisplay.h
new file mode 100644
index 00000000..db8a9519
--- /dev/null
+++ b/src/TypingDisplay.h
@@ -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_;
+};
diff --git a/src/UserInfoWidget.cc b/src/UserInfoWidget.cpp
index 092184f7..1470fc25 100644
--- a/src/UserInfoWidget.cc
+++ b/src/UserInfoWidget.cpp
@@ -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)
diff --git a/src/UserInfoWidget.h b/src/UserInfoWidget.h
new file mode 100644
index 00000000..ea2d5400
--- /dev/null
+++ b/src/UserInfoWidget.h
@@ -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_;
+};
diff --git a/src/UserSettingsPage.cc b/src/UserSettingsPage.cpp
index 7354e413..4c249369 100644
--- a/src/UserSettingsPage.cc
+++ b/src/UserSettingsPage.cpp
@@ -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(); }
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
new file mode 100644
index 00000000..177f1921
--- /dev/null
+++ b/src/UserSettingsPage.h
@@ -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;
+};
diff --git a/src/Utils.cc b/src/Utils.cpp
index 2247c2b7..2247c2b7 100644
--- a/src/Utils.cc
+++ b/src/Utils.cpp
diff --git a/src/Utils.h b/src/Utils.h
new file mode 100644
index 00000000..8f9b7cff
--- /dev/null
+++ b/src/Utils.h
@@ -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);
+}
diff --git a/src/WelcomePage.cc b/src/WelcomePage.cpp
index b21da92a..0560222e 100644
--- a/src/WelcomePage.cc
+++ b/src/WelcomePage.cpp
@@ -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)
diff --git a/src/WelcomePage.h b/src/WelcomePage.h
new file mode 100644
index 00000000..b33ca669
--- /dev/null
+++ b/src/WelcomePage.h
@@ -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_;
+};
diff --git a/src/dialogs/CreateRoom.cc b/src/dialogs/CreateRoom.cpp
index 8c2cc641..3c538b49 100644
--- a/src/dialogs/CreateRoom.cc
+++ b/src/dialogs/CreateRoom.cpp
@@ -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)
diff --git a/src/dialogs/CreateRoom.h b/src/dialogs/CreateRoom.h
new file mode 100644
index 00000000..46edebdc
--- /dev/null
+++ b/src/dialogs/CreateRoom.h
@@ -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
diff --git a/src/dialogs/ImageOverlay.cc b/src/dialogs/ImageOverlay.cpp
index 0e4d9d71..7773f97c 100644
--- a/src/dialogs/ImageOverlay.cc
+++ b/src/dialogs/ImageOverlay.cpp
@@ -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)
diff --git a/src/dialogs/ImageOverlay.h b/src/dialogs/ImageOverlay.h
new file mode 100644
index 00000000..b4d42acb
--- /dev/null
+++ b/src/dialogs/ImageOverlay.h
@@ -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
diff --git a/src/dialogs/InviteUsers.cc b/src/dialogs/InviteUsers.cpp
index 71cfdf20..bcd163b0 100644
--- a/src/dialogs/InviteUsers.cc
+++ b/src/dialogs/InviteUsers.cpp
@@ -6,12 +6,12 @@
#include <QTimer>
#include <QVBoxLayout>
-#include "Config.h"
-#include "FlatButton.h"
-#include "TextField.h"
+#include "dialogs/InviteUsers.h"
+#include "Config.h"
#include "InviteeItem.h"
-#include "dialogs/InviteUsers.h"
+#include "ui/FlatButton.h"
+#include "ui/TextField.h"
#include "mtx.hpp"
diff --git a/src/dialogs/InviteUsers.h b/src/dialogs/InviteUsers.h
new file mode 100644
index 00000000..41e6236a
--- /dev/null
+++ b/src/dialogs/InviteUsers.h
@@ -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
diff --git a/src/dialogs/JoinRoom.cc b/src/dialogs/JoinRoom.cpp
index d6e83014..05c0f455 100644
--- a/src/dialogs/JoinRoom.cc
+++ b/src/dialogs/JoinRoom.cpp
@@ -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)
diff --git a/src/dialogs/JoinRoom.h b/src/dialogs/JoinRoom.h
new file mode 100644
index 00000000..5919f08f
--- /dev/null
+++ b/src/dialogs/JoinRoom.h
@@ -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
diff --git a/src/dialogs/LeaveRoom.cc b/src/dialogs/LeaveRoom.cpp
index 508353c6..9647d19f 100644
--- a/src/dialogs/LeaveRoom.cc
+++ b/src/dialogs/LeaveRoom.cpp
@@ -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)
diff --git a/src/dialogs/LeaveRoom.h b/src/dialogs/LeaveRoom.h
new file mode 100644
index 00000000..98e4938d
--- /dev/null
+++ b/src/dialogs/LeaveRoom.h
@@ -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
diff --git a/src/dialogs/Logout.cc b/src/dialogs/Logout.cpp
index 99913b04..e2449817 100644
--- a/src/dialogs/Logout.cc
+++ b/src/dialogs/Logout.cpp
@@ -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)
diff --git a/src/dialogs/Logout.h b/src/dialogs/Logout.h
new file mode 100644
index 00000000..cfefb970
--- /dev/null
+++ b/src/dialogs/Logout.h
@@ -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
diff --git a/src/dialogs/MemberList.cpp b/src/dialogs/MemberList.cpp
index f0f61686..60c2eb0a 100644
--- a/src/dialogs/MemberList.cpp
+++ b/src/dialogs/MemberList.cpp
@@ -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;
diff --git a/src/dialogs/MemberList.h b/src/dialogs/MemberList.h
new file mode 100644
index 00000000..9c3dc5dc
--- /dev/null
+++ b/src/dialogs/MemberList.h
@@ -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
diff --git a/src/dialogs/PreviewUploadOverlay.cc b/src/dialogs/PreviewUploadOverlay.cpp
index a3fe4228..7e54ba4e 100644
--- a/src/dialogs/PreviewUploadOverlay.cc
+++ b/src/dialogs/PreviewUploadOverlay.cpp
@@ -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?";
diff --git a/src/dialogs/PreviewUploadOverlay.h b/src/dialogs/PreviewUploadOverlay.h
new file mode 100644
index 00000000..8d093c7f
--- /dev/null
+++ b/src/dialogs/PreviewUploadOverlay.h
@@ -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
diff --git a/src/dialogs/ReCaptcha.cpp b/src/dialogs/ReCaptcha.cpp
index ba54268c..9181d588 100644
--- a/src/dialogs/ReCaptcha.cpp
+++ b/src/dialogs/ReCaptcha.cpp
@@ -4,13 +4,13 @@
#include <QStyleOption>
#include <QVBoxLayout>
+#include "dialogs/ReCaptcha.h"
+
#include "Config.h"
-#include "FlatButton.h"
#include "MatrixClient.h"
-#include "RaisedButton.h"
-#include "Theme.h"
-
-#include "dialogs/ReCaptcha.hpp"
+#include "ui/FlatButton.h"
+#include "ui/RaisedButton.h"
+#include "ui/Theme.h"
using namespace dialogs;
diff --git a/src/dialogs/ReCaptcha.h b/src/dialogs/ReCaptcha.h
new file mode 100644
index 00000000..5f47b0eb
--- /dev/null
+++ b/src/dialogs/ReCaptcha.h
@@ -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
diff --git a/src/dialogs/ReadReceipts.cc b/src/dialogs/ReadReceipts.cpp
index a2e1faf2..c27146ac 100644
--- a/src/dialogs/ReadReceipts.cc
+++ b/src/dialogs/ReadReceipts.cpp
@@ -6,14 +6,14 @@
#include <QTimer>
#include <QVBoxLayout>
-#include "ChatPage.h"
-#include "Config.h"
-#include "Utils.h"
+#include "dialogs/ReadReceipts.h"
-#include "Avatar.h"
#include "AvatarProvider.h"
#include "Cache.h"
-#include "dialogs/ReadReceipts.h"
+#include "ChatPage.h"
+#include "Config.h"
+#include "Utils.h"
+#include "ui/Avatar.h"
using namespace dialogs;
diff --git a/src/dialogs/ReadReceipts.h b/src/dialogs/ReadReceipts.h
new file mode 100644
index 00000000..5e5615df
--- /dev/null
+++ b/src/dialogs/ReadReceipts.h
@@ -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
diff --git a/src/dialogs/RoomSettings.cpp b/src/dialogs/RoomSettings.cpp
index 3f40ae89..cfb25997 100644
--- a/src/dialogs/RoomSettings.cpp
+++ b/src/dialogs/RoomSettings.cpp
@@ -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;
diff --git a/src/dialogs/RoomSettings.h b/src/dialogs/RoomSettings.h
new file mode 100644
index 00000000..6cab03b7
--- /dev/null
+++ b/src/dialogs/RoomSettings.h
@@ -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
diff --git a/src/emoji/Category.cc b/src/emoji/Category.cpp
index c1b526f2..c1b526f2 100644
--- a/src/emoji/Category.cc
+++ b/src/emoji/Category.cpp
diff --git a/src/emoji/Category.h b/src/emoji/Category.h
new file mode 100644
index 00000000..a14029c8
--- /dev/null
+++ b/src/emoji/Category.h
@@ -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
diff --git a/src/emoji/ItemDelegate.cc b/src/emoji/ItemDelegate.cpp
index 2cc838e3..2cc838e3 100644
--- a/src/emoji/ItemDelegate.cc
+++ b/src/emoji/ItemDelegate.cpp
diff --git a/src/emoji/ItemDelegate.h b/src/emoji/ItemDelegate.h
new file mode 100644
index 00000000..e0456308
--- /dev/null
+++ b/src/emoji/ItemDelegate.h
@@ -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
diff --git a/src/emoji/Panel.cc b/src/emoji/Panel.cpp
index 89c3f823..710b501e 100644
--- a/src/emoji/Panel.cc
+++ b/src/emoji/Panel.cpp
@@ -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"
diff --git a/src/emoji/Panel.h b/src/emoji/Panel.h
new file mode 100644
index 00000000..ad233c27
--- /dev/null
+++ b/src/emoji/Panel.h
@@ -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
diff --git a/src/emoji/PickButton.cc b/src/emoji/PickButton.cpp
index d2b4e9fd..d2b4e9fd 100644
--- a/src/emoji/PickButton.cc
+++ b/src/emoji/PickButton.cpp
diff --git a/src/emoji/PickButton.h b/src/emoji/PickButton.h
new file mode 100644
index 00000000..d14067c6
--- /dev/null
+++ b/src/emoji/PickButton.h
@@ -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
diff --git a/src/emoji/Provider.cc b/src/emoji/Provider.cpp
index f7b8dab9..f7b8dab9 100644
--- a/src/emoji/Provider.cc
+++ b/src/emoji/Provider.cpp
diff --git a/src/emoji/Provider.h b/src/emoji/Provider.h
new file mode 100644
index 00000000..5cc3ced4
--- /dev/null
+++ b/src/emoji/Provider.h
@@ -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
diff --git a/src/main.cc b/src/main.cpp
index b776e902..d8bf30ce 100644
--- a/src/main.cc
+++ b/src/main.cpp
@@ -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>
diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h
new file mode 100644
index 00000000..4ac60097
--- /dev/null
+++ b/src/notifications/Manager.h
@@ -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
diff --git a/src/timeline/TimelineItem.cc b/src/timeline/TimelineItem.cpp
index d756ca26..88ab1963 100644
--- a/src/timeline/TimelineItem.cc
+++ b/src/timeline/TimelineItem.cpp
@@ -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"
diff --git a/src/timeline/TimelineItem.h b/src/timeline/TimelineItem.h
new file mode 100644
index 00000000..d3cab0a0
--- /dev/null
+++ b/src/timeline/TimelineItem.h
@@ -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();
+}
diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cpp
index 207844e4..a8c04807 100644
--- a/src/timeline/TimelineView.cc
+++ b/src/timeline/TimelineView.cpp
@@ -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"
diff --git a/src/timeline/TimelineView.h b/src/timeline/TimelineView.h
new file mode 100644
index 00000000..7b269063
--- /dev/null
+++ b/src/timeline/TimelineView.h
@@ -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;
+}
diff --git a/src/timeline/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cpp
index c8e00b66..1decab35 100644
--- a/src/timeline/TimelineViewManager.cc
+++ b/src/timeline/TimelineViewManager.cpp
@@ -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"
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
new file mode 100644
index 00000000..f3c099c1
--- /dev/null
+++ b/src/timeline/TimelineViewManager.h
@@ -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_;
+};
diff --git a/src/timeline/widgets/AudioItem.cc b/src/timeline/widgets/AudioItem.cpp
index 2ed4f4c0..1e3eb0f0 100644
--- a/src/timeline/widgets/AudioItem.cc
+++ b/src/timeline/widgets/AudioItem.cpp
@@ -22,7 +22,7 @@
#include <QPainter>
#include <QPixmap>
-#include "Logging.hpp"
+#include "Logging.h"
#include "MatrixClient.h"
#include "Utils.h"
diff --git a/src/timeline/widgets/AudioItem.h b/src/timeline/widgets/AudioItem.h
new file mode 100644
index 00000000..7b0781a2
--- /dev/null
+++ b/src/timeline/widgets/AudioItem.h
@@ -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");
+};
diff --git a/src/timeline/widgets/FileItem.cc b/src/timeline/widgets/FileItem.cpp
index b4555b2f..f8d3272d 100644
--- a/src/timeline/widgets/FileItem.cc
+++ b/src/timeline/widgets/FileItem.cpp
@@ -22,7 +22,7 @@
#include <QPainter>
#include <QPixmap>
-#include "Logging.hpp"
+#include "Logging.h"
#include "MatrixClient.h"
#include "Utils.h"
diff --git a/src/timeline/widgets/FileItem.h b/src/timeline/widgets/FileItem.h
new file mode 100644
index 00000000..66543e79
--- /dev/null
+++ b/src/timeline/widgets/FileItem.h
@@ -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");
+};
diff --git a/src/timeline/widgets/ImageItem.cc b/src/timeline/widgets/ImageItem.cpp
index b7adb0fa..19b445db 100644
--- a/src/timeline/widgets/ImageItem.cc
+++ b/src/timeline/widgets/ImageItem.cpp
@@ -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)
diff --git a/src/timeline/widgets/ImageItem.h b/src/timeline/widgets/ImageItem.h
new file mode 100644
index 00000000..e9d823f4
--- /dev/null
+++ b/src/timeline/widgets/ImageItem.h
@@ -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 <QEvent>
+#include <QMouseEvent>
+#include <QSharedPointer>
+#include <QWidget>
+
+#include <mtx.hpp>
+
+namespace dialogs {
+class ImageOverlay;
+}
+
+class ImageItem : public QWidget
+{
+ Q_OBJECT
+public:
+ ImageItem(const mtx::events::RoomEvent<mtx::events::msg::Image> &event,
+ QWidget *parent = nullptr);
+
+ ImageItem(const QString &url,
+ const QString &filename,
+ uint64_t size,
+ QWidget *parent = nullptr);
+
+ QSize sizeHint() const override;
+
+public slots:
+ //! Show a save as dialog for the image.
+ void saveAs();
+ void setImage(const QPixmap &image);
+ void saveImage(const QString &filename, const QByteArray &data);
+
+signals:
+ void imageDownloaded(const QPixmap &img);
+ void imageSaved(const QString &filename, const QByteArray &data);
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+
+ //! Whether the user can interact with the displayed image.
+ bool isInteractive_ = true;
+
+private:
+ void init();
+ void openUrl();
+ void downloadMedia(const QUrl &url);
+
+ int max_width_ = 500;
+ int max_height_ = 300;
+
+ int width_;
+ int height_;
+
+ QPixmap scaled_image_;
+ QPixmap image_;
+
+ QUrl url_;
+ QString text_;
+
+ int bottom_height_ = 30;
+
+ QRectF textRegion_;
+ QRectF imageRegion_;
+
+ mtx::events::RoomEvent<mtx::events::msg::Image> event_;
+};
+
+class StickerItem : public ImageItem
+{
+ Q_OBJECT
+
+public:
+ StickerItem(const mtx::events::Sticker &event, QWidget *parent = nullptr)
+ : ImageItem{QString::fromStdString(event.content.url),
+ QString::fromStdString(event.content.body),
+ event.content.info.size,
+ parent}
+ , event_{event}
+ {
+ isInteractive_ = false;
+ setCursor(Qt::ArrowCursor);
+ setMouseTracking(false);
+ setAttribute(Qt::WA_Hover, false);
+ }
+
+private:
+ mtx::events::Sticker event_;
+};
diff --git a/src/timeline/widgets/VideoItem.cc b/src/timeline/widgets/VideoItem.cpp
index daf181b2..daf181b2 100644
--- a/src/timeline/widgets/VideoItem.cc
+++ b/src/timeline/widgets/VideoItem.cpp
diff --git a/src/timeline/widgets/VideoItem.h b/src/timeline/widgets/VideoItem.h
new file mode 100644
index 00000000..26fa1c35
--- /dev/null
+++ b/src/timeline/widgets/VideoItem.h
@@ -0,0 +1,51 @@
+/*
+ * 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 <QLabel>
+#include <QSharedPointer>
+#include <QUrl>
+#include <QWidget>
+
+#include <mtx.hpp>
+
+class VideoItem : public QWidget
+{
+ Q_OBJECT
+
+public:
+ VideoItem(const mtx::events::RoomEvent<mtx::events::msg::Video> &event,
+ QWidget *parent = nullptr);
+
+ VideoItem(const QString &url,
+ const QString &filename,
+ uint64_t size,
+ QWidget *parent = nullptr);
+
+private:
+ void init();
+
+ QUrl url_;
+ QString text_;
+ QString readableFileSize_;
+
+ QLabel *label_;
+
+ mtx::events::RoomEvent<mtx::events::msg::Video> event_;
+};
diff --git a/src/ui/Avatar.cc b/src/ui/Avatar.cpp
index 2f10db39..4b4cd272 100644
--- a/src/ui/Avatar.cc
+++ b/src/ui/Avatar.cpp
@@ -1,7 +1,7 @@
#include <QPainter>
-#include "Avatar.h"
#include "Utils.h"
+#include "ui/Avatar.h"
Avatar::Avatar(QWidget *parent)
: QWidget(parent)
diff --git a/src/ui/Avatar.h b/src/ui/Avatar.h
new file mode 100644
index 00000000..41967af5
--- /dev/null
+++ b/src/ui/Avatar.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <QIcon>
+#include <QImage>
+#include <QPixmap>
+#include <QWidget>
+
+#include "Theme.h"
+
+class Avatar : public QWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
+ Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
+
+public:
+ explicit Avatar(QWidget *parent = 0);
+
+ void setBackgroundColor(const QColor &color);
+ void setIcon(const QIcon &icon);
+ void setImage(const QImage &image);
+ void setLetter(const QString &letter);
+ void setSize(int size);
+ void setTextColor(const QColor &color);
+
+ QColor backgroundColor() const;
+ QColor textColor() const;
+ int size() const;
+
+ QSize sizeHint() const override;
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+
+private:
+ void init();
+
+ ui::AvatarType type_;
+ QString letter_;
+ QColor background_color_;
+ QColor text_color_;
+ QIcon icon_;
+ QImage image_;
+ QPixmap pixmap_;
+ int size_;
+};
diff --git a/src/ui/Badge.cc b/src/ui/Badge.cpp
index 6701f9b7..6701f9b7 100644
--- a/src/ui/Badge.cc
+++ b/src/ui/Badge.cpp
diff --git a/src/ui/Badge.h b/src/ui/Badge.h
new file mode 100644
index 00000000..fd73ad30
--- /dev/null
+++ b/src/ui/Badge.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <QColor>
+#include <QIcon>
+#include <QWidget>
+#include <QtGlobal>
+
+#include "OverlayWidget.h"
+
+class Badge : public OverlayWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
+ Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
+ Q_PROPERTY(QPointF relativePosition WRITE setRelativePosition READ relativePosition)
+
+public:
+ explicit Badge(QWidget *parent = 0);
+ explicit Badge(const QIcon &icon, QWidget *parent = 0);
+ explicit Badge(const QString &text, QWidget *parent = 0);
+
+ void setBackgroundColor(const QColor &color);
+ void setTextColor(const QColor &color);
+ void setIcon(const QIcon &icon);
+ void setRelativePosition(const QPointF &pos);
+ void setRelativePosition(qreal x, qreal y);
+ void setRelativeXPosition(qreal x);
+ void setRelativeYPosition(qreal y);
+ void setText(const QString &text);
+ void setDiameter(int diameter);
+
+ QIcon icon() const;
+ QString text() const;
+ QColor backgroundColor() const;
+ QColor textColor() const;
+ QPointF relativePosition() const;
+ QSize sizeHint() const override;
+ qreal relativeXPosition() const;
+ qreal relativeYPosition() const;
+
+ int diameter() const;
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+
+private:
+ void init();
+
+ QColor background_color_;
+ QColor text_color_;
+
+ QIcon icon_;
+ QSize size_;
+ QString text_;
+
+ int padding_;
+ int diameter_;
+
+ qreal x_;
+ qreal y_;
+};
diff --git a/src/ui/DropShadow.h b/src/ui/DropShadow.h
new file mode 100644
index 00000000..b7ba1985
--- /dev/null
+++ b/src/ui/DropShadow.h
@@ -0,0 +1,111 @@
+#pragma once
+
+#include <QColor>
+#include <QLinearGradient>
+#include <QPainter>
+
+class DropShadow
+{
+public:
+ static void draw(QPainter &painter,
+ qint16 margin,
+ qreal radius,
+ QColor start,
+ QColor end,
+ qreal startPosition,
+ qreal endPosition0,
+ qreal endPosition1,
+ qreal width,
+ qreal height)
+ {
+ painter.setPen(Qt::NoPen);
+
+ QLinearGradient gradient;
+ gradient.setColorAt(startPosition, start);
+ gradient.setColorAt(endPosition0, end);
+
+ // Right
+ QPointF right0(width - margin, height / 2);
+ QPointF right1(width, height / 2);
+ gradient.setStart(right0);
+ gradient.setFinalStop(right1);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundRect(
+ QRectF(QPointF(width - margin * radius, margin), QPointF(width, height - margin)),
+ 0.0,
+ 0.0);
+
+ // Left
+ QPointF left0(margin, height / 2);
+ QPointF left1(0, height / 2);
+ gradient.setStart(left0);
+ gradient.setFinalStop(left1);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundRect(
+ QRectF(QPointF(margin * radius, margin), QPointF(0, height - margin)), 0.0, 0.0);
+
+ // Top
+ QPointF top0(width / 2, margin);
+ QPointF top1(width / 2, 0);
+ gradient.setStart(top0);
+ gradient.setFinalStop(top1);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundRect(
+ QRectF(QPointF(width - margin, 0), QPointF(margin, margin)), 0.0, 0.0);
+
+ // Bottom
+ QPointF bottom0(width / 2, height - margin);
+ QPointF bottom1(width / 2, height);
+ gradient.setStart(bottom0);
+ gradient.setFinalStop(bottom1);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundRect(
+ QRectF(QPointF(margin, height - margin), QPointF(width - margin, height)),
+ 0.0,
+ 0.0);
+
+ // BottomRight
+ QPointF bottomright0(width - margin, height - margin);
+ QPointF bottomright1(width, height);
+ gradient.setStart(bottomright0);
+ gradient.setFinalStop(bottomright1);
+ gradient.setColorAt(endPosition1, end);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundRect(QRectF(bottomright0, bottomright1), 0.0, 0.0);
+
+ // BottomLeft
+ QPointF bottomleft0(margin, height - margin);
+ QPointF bottomleft1(0, height);
+ gradient.setStart(bottomleft0);
+ gradient.setFinalStop(bottomleft1);
+ gradient.setColorAt(endPosition1, end);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundRect(QRectF(bottomleft0, bottomleft1), 0.0, 0.0);
+
+ // TopLeft
+ QPointF topleft0(margin, margin);
+ QPointF topleft1(0, 0);
+ gradient.setStart(topleft0);
+ gradient.setFinalStop(topleft1);
+ gradient.setColorAt(endPosition1, end);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundRect(QRectF(topleft0, topleft1), 0.0, 0.0);
+
+ // TopRight
+ QPointF topright0(width - margin, margin);
+ QPointF topright1(width, 0);
+ gradient.setStart(topright0);
+ gradient.setFinalStop(topright1);
+ gradient.setColorAt(endPosition1, end);
+ painter.setBrush(QBrush(gradient));
+ painter.drawRoundRect(QRectF(topright0, topright1), 0.0, 0.0);
+
+ // Widget
+ painter.setBrush(QBrush("#FFFFFF"));
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.drawRoundRect(
+ QRectF(QPointF(margin, margin), QPointF(width - margin, height - margin)),
+ radius,
+ radius);
+ }
+};
diff --git a/src/ui/FlatButton.cc b/src/ui/FlatButton.cpp
index 45a7683e..45a7683e 100644
--- a/src/ui/FlatButton.cc
+++ b/src/ui/FlatButton.cpp
diff --git a/src/ui/FlatButton.h b/src/ui/FlatButton.h
new file mode 100644
index 00000000..9c2bf425
--- /dev/null
+++ b/src/ui/FlatButton.h
@@ -0,0 +1,185 @@
+#pragma once
+
+#include <QPaintEvent>
+#include <QPainter>
+#include <QPushButton>
+#include <QStateMachine>
+
+#include "Theme.h"
+
+class RippleOverlay;
+class FlatButton;
+
+class FlatButtonStateMachine : public QStateMachine
+{
+ Q_OBJECT
+
+ Q_PROPERTY(qreal overlayOpacity WRITE setOverlayOpacity READ overlayOpacity)
+ Q_PROPERTY(
+ qreal checkedOverlayProgress WRITE setCheckedOverlayProgress READ checkedOverlayProgress)
+
+public:
+ explicit FlatButtonStateMachine(FlatButton *parent);
+ ~FlatButtonStateMachine();
+
+ void setOverlayOpacity(qreal opacity);
+ void setCheckedOverlayProgress(qreal opacity);
+
+ inline qreal overlayOpacity() const;
+ inline qreal checkedOverlayProgress() const;
+
+ void startAnimations();
+ void setupProperties();
+ void updateCheckedStatus();
+
+signals:
+ void buttonPressed();
+ void buttonChecked();
+ void buttonUnchecked();
+
+protected:
+ bool eventFilter(QObject *watched, QEvent *event) override;
+
+private:
+ void addTransition(QObject *object, const char *signal, QState *fromState, QState *toState);
+ void addTransition(QObject *object,
+ QEvent::Type eventType,
+ QState *fromState,
+ QState *toState);
+ void addTransition(QAbstractTransition *transition, QState *fromState, QState *toState);
+
+ FlatButton *const button_;
+
+ QState *const top_level_state_;
+ QState *const config_state_;
+ QState *const checkable_state_;
+ QState *const checked_state_;
+ QState *const unchecked_state_;
+ QState *const neutral_state_;
+ QState *const neutral_focused_state_;
+ QState *const hovered_state_;
+ QState *const hovered_focused_state_;
+ QState *const pressed_state_;
+
+ qreal overlay_opacity_;
+ qreal checked_overlay_progress_;
+
+ bool was_checked_;
+};
+
+inline qreal
+FlatButtonStateMachine::overlayOpacity() const
+{
+ return overlay_opacity_;
+}
+
+inline qreal
+FlatButtonStateMachine::checkedOverlayProgress() const
+{
+ return checked_overlay_progress_;
+}
+
+class FlatButton : public QPushButton
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QColor foregroundColor WRITE setForegroundColor READ foregroundColor)
+ Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
+ Q_PROPERTY(QColor overlayColor WRITE setOverlayColor READ overlayColor)
+ Q_PROPERTY(QColor disabledForegroundColor WRITE setDisabledForegroundColor READ
+ disabledForegroundColor)
+ Q_PROPERTY(QColor disabledBackgroundColor WRITE setDisabledBackgroundColor READ
+ disabledBackgroundColor)
+ Q_PROPERTY(qreal fontSize WRITE setFontSize READ fontSize)
+
+public:
+ explicit FlatButton(QWidget *parent = 0,
+ ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
+ explicit FlatButton(const QString &text,
+ QWidget *parent = 0,
+ ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
+ FlatButton(const QString &text,
+ ui::Role role,
+ QWidget *parent = 0,
+ ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
+ ~FlatButton();
+
+ void applyPreset(ui::ButtonPreset preset);
+
+ void setBackgroundColor(const QColor &color);
+ void setBackgroundMode(Qt::BGMode mode);
+ void setBaseOpacity(qreal opacity);
+ void setCheckable(bool value);
+ void setCornerRadius(qreal radius);
+ void setDisabledBackgroundColor(const QColor &color);
+ void setDisabledForegroundColor(const QColor &color);
+ void setFixedRippleRadius(qreal radius);
+ void setFontSize(qreal size);
+ void setForegroundColor(const QColor &color);
+ void setHasFixedRippleRadius(bool value);
+ void setIconPlacement(ui::ButtonIconPlacement placement);
+ void setOverlayColor(const QColor &color);
+ void setOverlayStyle(ui::OverlayStyle style);
+ void setRippleStyle(ui::RippleStyle style);
+ void setRole(ui::Role role);
+
+ QColor foregroundColor() const;
+ QColor backgroundColor() const;
+ QColor overlayColor() const;
+ QColor disabledForegroundColor() const;
+ QColor disabledBackgroundColor() const;
+
+ qreal fontSize() const;
+ qreal cornerRadius() const;
+ qreal baseOpacity() const;
+
+ bool hasFixedRippleRadius() const;
+
+ ui::Role role() const;
+ ui::OverlayStyle overlayStyle() const;
+ ui::RippleStyle rippleStyle() const;
+ ui::ButtonIconPlacement iconPlacement() const;
+
+ Qt::BGMode backgroundMode() const;
+
+ QSize sizeHint() const override;
+
+protected:
+ int IconPadding = 0;
+
+ void checkStateSet() override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
+
+ virtual void paintBackground(QPainter *painter);
+ virtual void paintForeground(QPainter *painter);
+ virtual void updateClipPath();
+
+ void init();
+
+private:
+ RippleOverlay *ripple_overlay_;
+ FlatButtonStateMachine *state_machine_;
+
+ ui::Role role_;
+ ui::RippleStyle ripple_style_;
+ ui::ButtonIconPlacement icon_placement_;
+ ui::OverlayStyle overlay_style_;
+
+ Qt::BGMode bg_mode_;
+
+ QColor background_color_;
+ QColor foreground_color_;
+ QColor overlay_color_;
+ QColor disabled_color_;
+ QColor disabled_background_color_;
+
+ qreal fixed_ripple_radius_;
+ qreal corner_radius_;
+ qreal base_opacity_;
+ qreal font_size_;
+
+ bool use_fixed_ripple_radius_;
+};
diff --git a/src/ui/FloatingButton.cc b/src/ui/FloatingButton.cpp
index 74dcd482..74dcd482 100644
--- a/src/ui/FloatingButton.cc
+++ b/src/ui/FloatingButton.cpp
diff --git a/src/ui/FloatingButton.h b/src/ui/FloatingButton.h
new file mode 100644
index 00000000..91e99ebb
--- /dev/null
+++ b/src/ui/FloatingButton.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "RaisedButton.h"
+
+constexpr int DIAMETER = 40;
+constexpr int ICON_SIZE = 18;
+
+constexpr int OFFSET_X = 30;
+constexpr int OFFSET_Y = 20;
+
+class FloatingButton : public RaisedButton
+{
+ Q_OBJECT
+
+public:
+ FloatingButton(const QIcon &icon, QWidget *parent = nullptr);
+
+ QSize sizeHint() const override { return QSize(DIAMETER, DIAMETER); };
+ QRect buttonGeometry() const;
+
+protected:
+ bool event(QEvent *event) override;
+ bool eventFilter(QObject *obj, QEvent *event) override;
+
+ void paintEvent(QPaintEvent *event) override;
+};
diff --git a/src/ui/InfoMessage.cpp b/src/ui/InfoMessage.cpp
index b150e61b..3151bedf 100644
--- a/src/ui/InfoMessage.cpp
+++ b/src/ui/InfoMessage.cpp
@@ -1,5 +1,5 @@
+#include "InfoMessage.h"
#include "Config.h"
-#include "InfoMessage.hpp"
#include <QDateTime>
#include <QPainter>
diff --git a/src/ui/InfoMessage.h b/src/ui/InfoMessage.h
new file mode 100644
index 00000000..58f98b0c
--- /dev/null
+++ b/src/ui/InfoMessage.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <QColor>
+#include <QDateTime>
+#include <QWidget>
+
+class InfoMessage : public QWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
+ Q_PROPERTY(QColor boxColor WRITE setBoxColor READ boxColor)
+
+public:
+ explicit InfoMessage(QWidget *parent = nullptr);
+ InfoMessage(QString msg, QWidget *parent = nullptr);
+
+ void setTextColor(QColor color) { textColor_ = color; }
+ void setBoxColor(QColor color) { boxColor_ = color; }
+ void saveDatetime(QDateTime datetime) { datetime_ = datetime; }
+
+ QColor textColor() const { return textColor_; }
+ QColor boxColor() const { return boxColor_; }
+ QDateTime datetime() const { return datetime_; }
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+
+ int width_;
+ int height_;
+
+ QString msg_;
+ QFont font_;
+
+ QDateTime datetime_;
+
+ QColor textColor_ = QColor("black");
+ QColor boxColor_ = QColor("white");
+};
+
+class DateSeparator : public InfoMessage
+{
+ Q_OBJECT
+
+public:
+ DateSeparator(QDateTime datetime, QWidget *parent = nullptr);
+};
diff --git a/src/ui/Label.cc b/src/ui/Label.cpp
index 8bd8c54e..8bd8c54e 100644
--- a/src/ui/Label.cc
+++ b/src/ui/Label.cpp
diff --git a/src/ui/Label.h b/src/ui/Label.h
new file mode 100644
index 00000000..09cf27d7
--- /dev/null
+++ b/src/ui/Label.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <QLabel>
+
+class Label : public QLabel
+{
+ Q_OBJECT
+
+public:
+ explicit Label(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
+ explicit Label(const QString &text,
+ QWidget *parent = Q_NULLPTR,
+ Qt::WindowFlags f = Qt::WindowFlags());
+
+signals:
+ void clicked(QMouseEvent *e);
+ void pressed(QMouseEvent *e);
+ void released(QMouseEvent *e);
+
+protected:
+ void mousePressEvent(QMouseEvent *e) override;
+ void mouseReleaseEvent(QMouseEvent *e) override;
+
+ QPoint pressPosition_;
+};
diff --git a/src/ui/LoadingIndicator.cc b/src/ui/LoadingIndicator.cpp
index f64151ce..f64151ce 100644
--- a/src/ui/LoadingIndicator.cc
+++ b/src/ui/LoadingIndicator.cpp
diff --git a/src/ui/LoadingIndicator.h b/src/ui/LoadingIndicator.h
new file mode 100644
index 00000000..bb33fe6c
--- /dev/null
+++ b/src/ui/LoadingIndicator.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <QColor>
+#include <QPaintEvent>
+#include <QPainter>
+#include <QTimer>
+#include <QWidget>
+
+class LoadingIndicator : public QWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(QColor color READ color WRITE setColor)
+
+public:
+ LoadingIndicator(QWidget *parent = 0);
+ virtual ~LoadingIndicator();
+
+ void paintEvent(QPaintEvent *e);
+
+ void start();
+ void stop();
+
+ QColor color() { return color_; }
+ void setColor(QColor color) { color_ = color; }
+
+ int interval() { return interval_; }
+ void setInterval(int interval) { interval_ = interval; }
+
+private slots:
+ void onTimeout();
+
+private:
+ int interval_;
+ int angle_;
+
+ QColor color_;
+ QTimer *timer_;
+};
diff --git a/src/ui/Menu.h b/src/ui/Menu.h
new file mode 100644
index 00000000..4c2a3c68
--- /dev/null
+++ b/src/ui/Menu.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <QMenu>
+
+#include "Config.h"
+
+class Menu : public QMenu
+{
+public:
+ Menu(QWidget *parent = nullptr)
+ : QMenu(parent)
+ {
+ QFont font;
+ font.setPixelSize(conf::fontSize);
+
+ setFont(font);
+ setStyleSheet(
+ "QMenu { color: black; background-color: white; margin: 0px;}"
+ "QMenu::item {"
+ "color: black; padding: 7px 20px; border: 1px solid transparent;"
+ "margin: 2px 0px; }"
+ "QMenu::item:selected { color: black; background: rgba(180, 180, 180, 100); }");
+ };
+
+protected:
+ void leaveEvent(QEvent *e)
+ {
+ Q_UNUSED(e);
+
+ hide();
+ }
+};
diff --git a/src/ui/OverlayModal.cc b/src/ui/OverlayModal.cpp
index 6aa16b07..6aa16b07 100644
--- a/src/ui/OverlayModal.cc
+++ b/src/ui/OverlayModal.cpp
diff --git a/src/ui/OverlayModal.h b/src/ui/OverlayModal.h
new file mode 100644
index 00000000..a761e3ed
--- /dev/null
+++ b/src/ui/OverlayModal.h
@@ -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 <QKeyEvent>
+#include <QMouseEvent>
+#include <QPaintEvent>
+
+#include "OverlayWidget.h"
+
+class OverlayModal : public OverlayWidget
+{
+public:
+ OverlayModal(QWidget *parent, QWidget *content);
+
+ void setColor(QColor color) { color_ = color; }
+ void setDismissible(bool state) { isDismissible_ = state; }
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+
+private:
+ QWidget *content_;
+ QColor color_;
+
+ //! Decides whether or not the modal can be removed by clicking into it.
+ bool isDismissible_ = true;
+};
diff --git a/src/ui/OverlayWidget.cc b/src/ui/OverlayWidget.cpp
index ccac0116..ccac0116 100644
--- a/src/ui/OverlayWidget.cc
+++ b/src/ui/OverlayWidget.cpp
diff --git a/src/ui/OverlayWidget.h b/src/ui/OverlayWidget.h
new file mode 100644
index 00000000..6662479d
--- /dev/null
+++ b/src/ui/OverlayWidget.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <QEvent>
+#include <QPainter>
+#include <QStyleOption>
+#include <QWidget>
+
+class OverlayWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit OverlayWidget(QWidget *parent = nullptr);
+
+protected:
+ bool event(QEvent *event) override;
+ bool eventFilter(QObject *obj, QEvent *event) override;
+
+ QRect overlayGeometry() const;
+ void paintEvent(QPaintEvent *event) override;
+};
diff --git a/src/ui/Painter.h b/src/ui/Painter.h
new file mode 100644
index 00000000..8de39651
--- /dev/null
+++ b/src/ui/Painter.h
@@ -0,0 +1,161 @@
+#pragma once
+
+#include <QFontMetrics>
+#include <QPaintDevice>
+#include <QPainter>
+
+class Painter : public QPainter
+{
+public:
+ explicit Painter(QPaintDevice *device)
+ : QPainter(device)
+ {}
+
+ void drawTextLeft(int x, int y, const QString &text)
+ {
+ QFontMetrics m(fontMetrics());
+ drawText(x, y + m.ascent(), text);
+ }
+
+ void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1)
+ {
+ QFontMetrics m(fontMetrics());
+ if (textWidth < 0)
+ textWidth = m.width(text);
+ drawText((outerw - x - textWidth), y + m.ascent(), text);
+ }
+
+ void drawPixmapLeft(int x, int y, const QPixmap &pix, const QRect &from)
+ {
+ drawPixmap(QPoint(x, y), pix, from);
+ }
+
+ void drawPixmapLeft(const QPoint &p, const QPixmap &pix, const QRect &from)
+ {
+ return drawPixmapLeft(p.x(), p.y(), pix, from);
+ }
+
+ void drawPixmapLeft(int x, int y, int w, int h, const QPixmap &pix, const QRect &from)
+ {
+ drawPixmap(QRect(x, y, w, h), pix, from);
+ }
+
+ void drawPixmapLeft(const QRect &r, const QPixmap &pix, const QRect &from)
+ {
+ return drawPixmapLeft(r.x(), r.y(), r.width(), r.height(), pix, from);
+ }
+
+ void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix)
+ {
+ Q_UNUSED(outerw);
+ drawPixmap(QPoint(x, y), pix);
+ }
+
+ void drawPixmapLeft(const QPoint &p, int outerw, const QPixmap &pix)
+ {
+ return drawPixmapLeft(p.x(), p.y(), outerw, pix);
+ }
+
+ void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix, const QRect &from)
+ {
+ drawPixmap(
+ QPoint((outerw - x - (from.width() / pix.devicePixelRatio())), y), pix, from);
+ }
+
+ void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from)
+ {
+ return drawPixmapRight(p.x(), p.y(), outerw, pix, from);
+ }
+ void drawPixmapRight(int x,
+ int y,
+ int w,
+ int h,
+ int outerw,
+ const QPixmap &pix,
+ const QRect &from)
+ {
+ drawPixmap(QRect((outerw - x - w), y, w, h), pix, from);
+ }
+
+ void drawPixmapRight(const QRect &r, int outerw, const QPixmap &pix, const QRect &from)
+ {
+ return drawPixmapRight(r.x(), r.y(), r.width(), r.height(), outerw, pix, from);
+ }
+
+ void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix)
+ {
+ drawPixmap(QPoint((outerw - x - (pix.width() / pix.devicePixelRatio())), y), pix);
+ }
+
+ void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix)
+ {
+ return drawPixmapRight(p.x(), p.y(), outerw, pix);
+ }
+
+ void drawAvatar(const QPixmap &pix, int w, int h, int d)
+ {
+ QPainterPath pp;
+ pp.addEllipse((w - d) / 2, (h - d) / 2, d, d);
+
+ QRect region((w - d) / 2, (h - d) / 2, d, d);
+
+ setClipPath(pp);
+ drawPixmap(region, pix);
+ }
+
+ void drawLetterAvatar(const QString &c,
+ const QColor &penColor,
+ const QColor &brushColor,
+ int w,
+ int h,
+ int d)
+ {
+ QRect region((w - d) / 2, (h - d) / 2, d, d);
+
+ setPen(Qt::NoPen);
+ setBrush(brushColor);
+
+ drawEllipse(region.center(), d / 2, d / 2);
+
+ setBrush(Qt::NoBrush);
+ drawEllipse(region.center(), d / 2, d / 2);
+
+ setPen(penColor);
+ drawText(region.translated(0, -1), Qt::AlignCenter, c);
+ }
+};
+
+class PainterHighQualityEnabler
+{
+public:
+ PainterHighQualityEnabler(Painter &p)
+ : _painter(p)
+ {
+ static constexpr QPainter::RenderHint Hints[] = {QPainter::Antialiasing,
+ QPainter::SmoothPixmapTransform,
+ QPainter::TextAntialiasing,
+ QPainter::HighQualityAntialiasing};
+
+ auto hints = _painter.renderHints();
+ for (const auto &hint : Hints) {
+ if (!(hints & hint))
+ hints_ |= hint;
+ }
+
+ if (hints_)
+ _painter.setRenderHints(hints_);
+ }
+
+ ~PainterHighQualityEnabler()
+ {
+ if (hints_)
+ _painter.setRenderHints(hints_, false);
+ }
+
+ PainterHighQualityEnabler(const PainterHighQualityEnabler &other) = delete;
+ PainterHighQualityEnabler &operator=(const PainterHighQualityEnabler &other) = delete;
+
+private:
+ Painter &_painter;
+ QPainter::RenderHints hints_ = 0;
+};
diff --git a/src/ui/RaisedButton.cc b/src/ui/RaisedButton.cpp
index c519f84f..c519f84f 100644
--- a/src/ui/RaisedButton.cc
+++ b/src/ui/RaisedButton.cpp
diff --git a/src/ui/RaisedButton.h b/src/ui/RaisedButton.h
new file mode 100644
index 00000000..edd5ee4a
--- /dev/null
+++ b/src/ui/RaisedButton.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <QGraphicsDropShadowEffect>
+#include <QState>
+#include <QStateMachine>
+
+#include "FlatButton.h"
+
+class RaisedButton : public FlatButton
+{
+ Q_OBJECT
+
+public:
+ explicit RaisedButton(QWidget *parent = 0);
+ explicit RaisedButton(const QString &text, QWidget *parent = 0);
+ ~RaisedButton();
+
+protected:
+ bool event(QEvent *event) override;
+
+private:
+ void init();
+
+ QStateMachine *shadow_state_machine_;
+ QState *normal_state_;
+ QState *pressed_state_;
+ QGraphicsDropShadowEffect *effect_;
+};
diff --git a/src/ui/Ripple.cc b/src/ui/Ripple.cpp
index e22c4a62..e22c4a62 100644
--- a/src/ui/Ripple.cc
+++ b/src/ui/Ripple.cpp
diff --git a/src/ui/Ripple.h b/src/ui/Ripple.h
new file mode 100644
index 00000000..9184f061
--- /dev/null
+++ b/src/ui/Ripple.h
@@ -0,0 +1,145 @@
+#pragma once
+
+#include <QBrush>
+#include <QEasingCurve>
+#include <QParallelAnimationGroup>
+#include <QPoint>
+#include <QPropertyAnimation>
+
+class RippleOverlay;
+
+class Ripple : public QParallelAnimationGroup
+{
+ Q_OBJECT
+
+ Q_PROPERTY(qreal radius WRITE setRadius READ radius)
+ Q_PROPERTY(qreal opacity WRITE setOpacity READ opacity)
+
+public:
+ explicit Ripple(const QPoint ¢er, QObject *parent = 0);
+ Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent = 0);
+
+ inline void setOverlay(RippleOverlay *overlay);
+
+ void setRadius(qreal radius);
+ void setOpacity(qreal opacity);
+ void setColor(const QColor &color);
+ void setBrush(const QBrush &brush);
+
+ inline qreal radius() const;
+ inline qreal opacity() const;
+ inline QColor color() const;
+ inline QBrush brush() const;
+ inline QPoint center() const;
+
+ inline QPropertyAnimation *radiusAnimation() const;
+ inline QPropertyAnimation *opacityAnimation() const;
+
+ inline void setOpacityStartValue(qreal value);
+ inline void setOpacityEndValue(qreal value);
+ inline void setRadiusStartValue(qreal value);
+ inline void setRadiusEndValue(qreal value);
+ inline void setDuration(int msecs);
+
+protected slots:
+ void destroy();
+
+private:
+ Q_DISABLE_COPY(Ripple)
+
+ QPropertyAnimation *animate(const QByteArray &property,
+ const QEasingCurve &easing = QEasingCurve::OutQuad,
+ int duration = 800);
+
+ void init();
+
+ RippleOverlay *overlay_;
+
+ QPropertyAnimation *const radius_anim_;
+ QPropertyAnimation *const opacity_anim_;
+
+ qreal radius_;
+ qreal opacity_;
+
+ QPoint center_;
+ QBrush brush_;
+};
+
+inline void
+Ripple::setOverlay(RippleOverlay *overlay)
+{
+ overlay_ = overlay;
+}
+
+inline qreal
+Ripple::radius() const
+{
+ return radius_;
+}
+
+inline qreal
+Ripple::opacity() const
+{
+ return opacity_;
+}
+
+inline QColor
+Ripple::color() const
+{
+ return brush_.color();
+}
+
+inline QBrush
+Ripple::brush() const
+{
+ return brush_;
+}
+
+inline QPoint
+Ripple::center() const
+{
+ return center_;
+}
+
+inline QPropertyAnimation *
+Ripple::radiusAnimation() const
+{
+ return radius_anim_;
+}
+
+inline QPropertyAnimation *
+Ripple::opacityAnimation() const
+{
+ return opacity_anim_;
+}
+
+inline void
+Ripple::setOpacityStartValue(qreal value)
+{
+ opacity_anim_->setStartValue(value);
+}
+
+inline void
+Ripple::setOpacityEndValue(qreal value)
+{
+ opacity_anim_->setEndValue(value);
+}
+
+inline void
+Ripple::setRadiusStartValue(qreal value)
+{
+ radius_anim_->setStartValue(value);
+}
+
+inline void
+Ripple::setRadiusEndValue(qreal value)
+{
+ radius_anim_->setEndValue(value);
+}
+
+inline void
+Ripple::setDuration(int msecs)
+{
+ radius_anim_->setDuration(msecs);
+ opacity_anim_->setDuration(msecs);
+}
diff --git a/src/ui/RippleOverlay.cc b/src/ui/RippleOverlay.cpp
index 20e98c0f..20e98c0f 100644
--- a/src/ui/RippleOverlay.cc
+++ b/src/ui/RippleOverlay.cpp
diff --git a/src/ui/RippleOverlay.h b/src/ui/RippleOverlay.h
new file mode 100644
index 00000000..9ef91fbf
--- /dev/null
+++ b/src/ui/RippleOverlay.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <QPainterPath>
+
+#include "OverlayWidget.h"
+
+class Ripple;
+
+class RippleOverlay : public OverlayWidget
+{
+ Q_OBJECT
+
+public:
+ explicit RippleOverlay(QWidget *parent = 0);
+
+ void addRipple(Ripple *ripple);
+ void addRipple(const QPoint &position, qreal radius = 300);
+
+ void removeRipple(Ripple *ripple);
+
+ inline void setClipping(bool enable);
+ inline bool hasClipping() const;
+
+ inline void setClipPath(const QPainterPath &path);
+
+protected:
+ void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
+
+private:
+ Q_DISABLE_COPY(RippleOverlay)
+
+ void paintRipple(QPainter *painter, Ripple *ripple);
+
+ QList<Ripple *> ripples_;
+ QPainterPath clip_path_;
+ bool use_clip_;
+};
+
+inline void
+RippleOverlay::setClipping(bool enable)
+{
+ use_clip_ = enable;
+ update();
+}
+
+inline bool
+RippleOverlay::hasClipping() const
+{
+ return use_clip_;
+}
+
+inline void
+RippleOverlay::setClipPath(const QPainterPath &path)
+{
+ clip_path_ = path;
+ update();
+}
diff --git a/src/ui/ScrollBar.cc b/src/ui/ScrollBar.cpp
index 37218a13..37218a13 100644
--- a/src/ui/ScrollBar.cc
+++ b/src/ui/ScrollBar.cpp
diff --git a/src/ui/ScrollBar.h b/src/ui/ScrollBar.h
new file mode 100644
index 00000000..2b5382aa
--- /dev/null
+++ b/src/ui/ScrollBar.h
@@ -0,0 +1,54 @@
+/*
+ * 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 <QPainter>
+#include <QScrollArea>
+#include <QScrollBar>
+
+class ScrollBar : public QScrollBar
+{
+ Q_OBJECT
+ Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
+ Q_PROPERTY(QColor handleColor READ handleColor WRITE setHandleColor)
+
+public:
+ ScrollBar(QScrollArea *area, QWidget *parent = nullptr);
+
+ QColor backgroundColor() const { return bgColor_; }
+ void setBackgroundColor(QColor &color) { bgColor_ = color; }
+
+ QColor handleColor() const { return handleColor_; }
+ void setHandleColor(QColor &color) { handleColor_ = color; }
+
+protected:
+ void paintEvent(QPaintEvent *e) override;
+
+private:
+ int roundRadius_ = 4;
+ int handleWidth_ = 7;
+ int minHandleHeight_ = 20;
+
+ const int Padding = 4;
+
+ QScrollArea *area_;
+ QRect handle_;
+
+ QColor bgColor_ = QColor(33, 33, 33, 30);
+ QColor handleColor_ = QColor(0, 0, 0, 80);
+};
diff --git a/src/ui/SnackBar.cc b/src/ui/SnackBar.cpp
index 43a4c85d..43a4c85d 100644
--- a/src/ui/SnackBar.cc
+++ b/src/ui/SnackBar.cpp
diff --git a/src/ui/SnackBar.h b/src/ui/SnackBar.h
new file mode 100644
index 00000000..eed59c87
--- /dev/null
+++ b/src/ui/SnackBar.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include <QCoreApplication>
+#include <QPaintEvent>
+#include <QTimer>
+#include <deque>
+
+#include "OverlayWidget.h"
+
+enum class SnackBarPosition
+{
+ Bottom,
+ Top,
+};
+
+class SnackBar : public OverlayWidget
+{
+ Q_OBJECT
+
+public:
+ explicit SnackBar(QWidget *parent);
+
+ inline void setBackgroundColor(const QColor &color);
+ inline void setTextColor(const QColor &color);
+ inline void setPosition(SnackBarPosition pos);
+
+public slots:
+ void showMessage(const QString &msg);
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+
+private slots:
+ void hideMessage();
+
+private:
+ void stopTimers();
+ void start();
+
+ QColor bgColor_;
+ QColor textColor_;
+
+ qreal bgOpacity_;
+ qreal offset_;
+
+ std::deque<QString> messages_;
+
+ QTimer showTimer_;
+ QTimer hideTimer_;
+
+ int duration_;
+ int boxWidth_;
+ int boxHeight_;
+ int boxPadding_;
+
+ SnackBarPosition position_;
+};
+
+inline void
+SnackBar::setPosition(SnackBarPosition pos)
+{
+ position_ = pos;
+ update();
+}
+
+inline void
+SnackBar::setBackgroundColor(const QColor &color)
+{
+ bgColor_ = color;
+ update();
+}
+
+inline void
+SnackBar::setTextColor(const QColor &color)
+{
+ textColor_ = color;
+ update();
+}
diff --git a/src/ui/TextField.cc b/src/ui/TextField.cpp
index 0c936e69..0c936e69 100644
--- a/src/ui/TextField.cc
+++ b/src/ui/TextField.cpp
diff --git a/src/ui/TextField.h b/src/ui/TextField.h
new file mode 100644
index 00000000..1675a2e0
--- /dev/null
+++ b/src/ui/TextField.h
@@ -0,0 +1,174 @@
+#pragma once
+
+#include <QColor>
+#include <QLineEdit>
+#include <QPaintEvent>
+#include <QPropertyAnimation>
+#include <QStateMachine>
+#include <QtGlobal>
+
+class TextField;
+class TextFieldLabel;
+class TextFieldStateMachine;
+
+class TextField : public QLineEdit
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
+ Q_PROPERTY(QColor inkColor WRITE setInkColor READ inkColor)
+ Q_PROPERTY(QColor labelColor WRITE setLabelColor READ labelColor)
+ Q_PROPERTY(QColor underlineColor WRITE setUnderlineColor READ underlineColor)
+ Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
+
+public:
+ explicit TextField(QWidget *parent = 0);
+
+ void setInkColor(const QColor &color);
+ void setBackgroundColor(const QColor &color);
+ void setLabel(const QString &label);
+ void setLabelColor(const QColor &color);
+ void setLabelFontSize(qreal size);
+ void setShowLabel(bool value);
+ void setTextColor(const QColor &color);
+ void setUnderlineColor(const QColor &color);
+
+ QColor inkColor() const;
+ QColor labelColor() const;
+ QColor textColor() const;
+ QColor underlineColor() const;
+ QColor backgroundColor() const;
+ QString label() const;
+ bool hasLabel() const;
+ qreal labelFontSize() const;
+
+protected:
+ bool event(QEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
+
+private:
+ void init();
+
+ QColor ink_color_;
+ QColor background_color_;
+ QColor label_color_;
+ QColor text_color_;
+ QColor underline_color_;
+ QString label_text_;
+ TextFieldLabel *label_;
+ TextFieldStateMachine *state_machine_;
+ bool show_label_;
+ qreal label_font_size_;
+};
+
+class TextFieldLabel : public QWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(qreal scale WRITE setScale READ scale)
+ Q_PROPERTY(QPointF offset WRITE setOffset READ offset)
+ Q_PROPERTY(QColor color WRITE setColor READ color)
+
+public:
+ TextFieldLabel(TextField *parent);
+
+ inline void setColor(const QColor &color);
+ inline void setOffset(const QPointF &pos);
+ inline void setScale(qreal scale);
+
+ inline QColor color() const;
+ inline QPointF offset() const;
+ inline qreal scale() const;
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+
+private:
+ TextField *const text_field_;
+
+ QColor color_;
+ qreal scale_;
+ qreal x_;
+ qreal y_;
+};
+
+inline void
+TextFieldLabel::setColor(const QColor &color)
+{
+ color_ = color;
+ update();
+}
+
+inline void
+TextFieldLabel::setOffset(const QPointF &pos)
+{
+ x_ = pos.x();
+ y_ = pos.y();
+ update();
+}
+
+inline void
+TextFieldLabel::setScale(qreal scale)
+{
+ scale_ = scale;
+ update();
+}
+
+inline QPointF
+TextFieldLabel::offset() const
+{
+ return QPointF(x_, y_);
+}
+inline qreal
+TextFieldLabel::scale() const
+{
+ return scale_;
+}
+inline QColor
+TextFieldLabel::color() const
+{
+ return color_;
+}
+
+class TextFieldStateMachine : public QStateMachine
+{
+ Q_OBJECT
+
+ Q_PROPERTY(qreal progress WRITE setProgress READ progress)
+
+public:
+ TextFieldStateMachine(TextField *parent);
+
+ inline void setProgress(qreal progress);
+ void setLabel(TextFieldLabel *label);
+
+ inline qreal progress() const;
+
+public slots:
+ void setupProperties();
+
+private:
+ QPropertyAnimation *color_anim_;
+ QPropertyAnimation *offset_anim_;
+
+ QState *focused_state_;
+ QState *normal_state_;
+
+ TextField *text_field_;
+ TextFieldLabel *label_;
+
+ qreal progress_;
+};
+
+inline void
+TextFieldStateMachine::setProgress(qreal progress)
+{
+ progress_ = progress;
+ text_field_->update();
+}
+
+inline qreal
+TextFieldStateMachine::progress() const
+{
+ return progress_;
+}
diff --git a/src/ui/Theme.cc b/src/ui/Theme.cpp
index 7209864a..7209864a 100644
--- a/src/ui/Theme.cc
+++ b/src/ui/Theme.cpp
diff --git a/src/ui/Theme.h b/src/ui/Theme.h
new file mode 100644
index 00000000..7a0bdcb7
--- /dev/null
+++ b/src/ui/Theme.h
@@ -0,0 +1,97 @@
+#pragma once
+
+#include <QColor>
+#include <QHash>
+#include <QObject>
+
+namespace ui {
+enum class AvatarType
+{
+ Icon,
+ Image,
+ Letter
+};
+
+namespace sidebar {
+static const int SmallSize = 60;
+static const int NormalSize = 260;
+static const int CommunitiesSidebarSize = 48;
+}
+// Default font size.
+const int FontSize = 16;
+
+// Default avatar size. Width and height.
+const int AvatarSize = 40;
+
+enum class ButtonPreset
+{
+ FlatPreset,
+ CheckablePreset
+};
+
+enum class RippleStyle
+{
+ CenteredRipple,
+ PositionedRipple,
+ NoRipple
+};
+
+enum class OverlayStyle
+{
+ NoOverlay,
+ TintedOverlay,
+ GrayOverlay
+};
+
+enum class Role
+{
+ Default,
+ Primary,
+ Secondary
+};
+
+enum class ButtonIconPlacement
+{
+ LeftIcon,
+ RightIcon
+};
+
+enum class ProgressType
+{
+ DeterminateProgress,
+ IndeterminateProgress
+};
+
+enum class Color
+{
+ Black,
+ BrightWhite,
+ FadedWhite,
+ MediumWhite,
+ DarkGreen,
+ LightGreen,
+ BrightGreen,
+ Gray,
+ Red,
+ Blue,
+ Transparent
+};
+
+} // namespace ui
+
+class Theme : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Theme(QObject *parent = 0);
+
+ QColor getColor(const QString &key) const;
+
+ void setColor(const QString &key, const QColor &color);
+ void setColor(const QString &key, ui::Color color);
+
+private:
+ QColor rgba(int r, int g, int b, qreal a) const;
+
+ QHash<QString, QColor> colors_;
+};
diff --git a/src/ui/ThemeManager.cc b/src/ui/ThemeManager.cpp
index 7baed1f3..7baed1f3 100644
--- a/src/ui/ThemeManager.cc
+++ b/src/ui/ThemeManager.cpp
diff --git a/src/ui/ThemeManager.h b/src/ui/ThemeManager.h
new file mode 100644
index 00000000..d35ff754
--- /dev/null
+++ b/src/ui/ThemeManager.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <QCommonStyle>
+
+#include "Theme.h"
+
+class ThemeManager : public QCommonStyle
+{
+ Q_OBJECT
+
+public:
+ inline static ThemeManager &instance();
+
+ void setTheme(Theme *theme);
+ QColor themeColor(const QString &key) const;
+
+private:
+ ThemeManager();
+
+ ThemeManager(ThemeManager const &);
+ void operator=(ThemeManager const &);
+
+ Theme *theme_;
+};
+
+inline ThemeManager &
+ThemeManager::instance()
+{
+ static ThemeManager instance;
+ return instance;
+}
diff --git a/src/ui/ToggleButton.cc b/src/ui/ToggleButton.cpp
index 755f528f..755f528f 100644
--- a/src/ui/ToggleButton.cc
+++ b/src/ui/ToggleButton.cpp
diff --git a/src/ui/ToggleButton.h b/src/ui/ToggleButton.h
new file mode 100644
index 00000000..14c3450b
--- /dev/null
+++ b/src/ui/ToggleButton.h
@@ -0,0 +1,110 @@
+#pragma once
+
+#include <QAbstractButton>
+#include <QColor>
+
+class ToggleTrack;
+class ToggleThumb;
+
+enum class Position
+{
+ Left,
+ Right
+};
+
+class Toggle : public QAbstractButton
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QColor activeColor WRITE setActiveColor READ activeColor)
+ Q_PROPERTY(QColor disabledColor WRITE setDisabledColor READ disabledColor)
+ Q_PROPERTY(QColor inactiveColor WRITE setInactiveColor READ inactiveColor)
+ Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor)
+
+public:
+ Toggle(QWidget *parent = nullptr);
+
+ void setState(bool isEnabled);
+
+ void setActiveColor(const QColor &color);
+ void setDisabledColor(const QColor &color);
+ void setInactiveColor(const QColor &color);
+ void setTrackColor(const QColor &color);
+
+ QColor activeColor() const { return activeColor_; };
+ QColor disabledColor() const { return disabledColor_; };
+ QColor inactiveColor() const { return inactiveColor_; };
+ QColor trackColor() const { return trackColor_.isValid() ? trackColor_ : QColor("#eee"); };
+
+ QSize sizeHint() const override { return QSize(64, 48); };
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+
+private:
+ void init();
+ void setupProperties();
+
+ ToggleTrack *track_;
+ ToggleThumb *thumb_;
+
+ QColor disabledColor_;
+ QColor activeColor_;
+ QColor inactiveColor_;
+ QColor trackColor_;
+};
+
+class ToggleThumb : public QWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QColor thumbColor WRITE setThumbColor READ thumbColor)
+
+public:
+ ToggleThumb(Toggle *parent);
+
+ Position shift() const { return position_; };
+ qreal offset() const { return offset_; };
+ QColor thumbColor() const { return thumbColor_; };
+
+ void setShift(Position position);
+ void setThumbColor(const QColor &color)
+ {
+ thumbColor_ = color;
+ update();
+ };
+
+protected:
+ bool eventFilter(QObject *obj, QEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
+
+private:
+ void updateOffset();
+
+ Toggle *const toggle_;
+ QColor thumbColor_;
+
+ Position position_;
+ qreal offset_;
+};
+
+class ToggleTrack : public QWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor)
+
+public:
+ ToggleTrack(Toggle *parent);
+
+ void setTrackColor(const QColor &color);
+ QColor trackColor() const { return trackColor_; };
+
+protected:
+ bool eventFilter(QObject *obj, QEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
+
+private:
+ Toggle *const toggle_;
+ QColor trackColor_;
+};
|