summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorKonstantinos Sideris <sideris.konstantin@gmail.com>2018-07-17 16:37:25 +0300
committerKonstantinos Sideris <sideris.konstantin@gmail.com>2018-07-17 16:37:25 +0300
commit0e814da91c8e041897a4c3f7e6e9234bbc7c6f7a (patch)
tree21f655d30630fe77ba48d07e4b357e2b6c6a5730 /src
parentMerge pull request #372 from bebehei/notification (diff)
downloadnheko-0e814da91c8e041897a4c3f7e6e9234bbc7c6f7a.tar.xz
Move all files under src/
Diffstat (limited to 'src')
-rw-r--r--src/AvatarProvider.cpp (renamed from src/AvatarProvider.cc)2
-rw-r--r--src/AvatarProvider.h36
-rw-r--r--src/Cache.cpp (renamed from src/Cache.cc)1
-rw-r--r--src/Cache.h661
-rw-r--r--src/ChatPage.cpp (renamed from src/ChatPage.cc)8
-rw-r--r--src/ChatPage.h268
-rw-r--r--src/CommunitiesList.cpp (renamed from src/CommunitiesList.cc)2
-rw-r--r--src/CommunitiesList.h50
-rw-r--r--src/CommunitiesListItem.cpp (renamed from src/CommunitiesListItem.cc)6
-rw-r--r--src/CommunitiesListItem.h88
-rw-r--r--src/Config.h106
-rw-r--r--src/InviteeItem.cpp (renamed from src/InviteeItem.cc)4
-rw-r--r--src/InviteeItem.h27
-rw-r--r--src/Logging.cpp2
-rw-r--r--src/Logging.h21
-rw-r--r--src/LoginPage.cpp (renamed from src/LoginPage.cc)10
-rw-r--r--src/LoginPage.h124
-rw-r--r--src/MainWindow.cpp (renamed from src/MainWindow.cc)12
-rw-r--r--src/MainWindow.h174
-rw-r--r--src/MatrixClient.cpp (renamed from src/MatrixClient.cc)0
-rw-r--r--src/MatrixClient.h30
-rw-r--r--src/Olm.cpp4
-rw-r--r--src/Olm.h86
-rw-r--r--src/QuickSwitcher.cpp (renamed from src/QuickSwitcher.cc)1
-rw-r--r--src/QuickSwitcher.h79
-rw-r--r--src/RegisterPage.cpp (renamed from src/RegisterPage.cc)10
-rw-r--r--src/RegisterPage.h84
-rw-r--r--src/RoomInfoListItem.cpp (renamed from src/RoomInfoListItem.cc)8
-rw-r--r--src/RoomInfoListItem.h204
-rw-r--r--src/RoomList.cpp (renamed from src/RoomList.cc)4
-rw-r--r--src/RoomList.h108
-rw-r--r--src/RunGuard.cpp (renamed from src/RunGuard.cc)0
-rw-r--r--src/RunGuard.h31
-rw-r--r--src/SideBarActions.cpp (renamed from src/SideBarActions.cc)6
-rw-r--r--src/SideBarActions.h50
-rw-r--r--src/Splitter.cpp (renamed from src/Splitter.cc)2
-rw-r--r--src/Splitter.h46
-rw-r--r--src/SuggestionsPopup.cpp13
-rw-r--r--src/SuggestionsPopup.h147
-rw-r--r--src/TextInputWidget.cpp (renamed from src/TextInputWidget.cc)2
-rw-r--r--src/TextInputWidget.h183
-rw-r--r--src/TopRoomBar.cpp (renamed from src/TopRoomBar.cc)8
-rw-r--r--src/TopRoomBar.h107
-rw-r--r--src/TrayIcon.cpp (renamed from src/TrayIcon.cc)0
-rw-r--r--src/TrayIcon.h59
-rw-r--r--src/TypingDisplay.cpp (renamed from src/TypingDisplay.cc)0
-rw-r--r--src/TypingDisplay.h21
-rw-r--r--src/UserInfoWidget.cpp (renamed from src/UserInfoWidget.cc)6
-rw-r--r--src/UserInfoWidget.h73
-rw-r--r--src/UserSettingsPage.cpp (renamed from src/UserSettingsPage.cc)6
-rw-r--r--src/UserSettingsPage.h148
-rw-r--r--src/Utils.cpp (renamed from src/Utils.cc)0
-rw-r--r--src/Utils.h194
-rw-r--r--src/WelcomePage.cpp (renamed from src/WelcomePage.cc)2
-rw-r--r--src/WelcomePage.h44
-rw-r--r--src/dialogs/CreateRoom.cpp (renamed from src/dialogs/CreateRoom.cc)12
-rw-r--r--src/dialogs/CreateRoom.h45
-rw-r--r--src/dialogs/ImageOverlay.cpp (renamed from src/dialogs/ImageOverlay.cc)3
-rw-r--r--src/dialogs/ImageOverlay.h47
-rw-r--r--src/dialogs/InviteUsers.cpp (renamed from src/dialogs/InviteUsers.cc)8
-rw-r--r--src/dialogs/InviteUsers.h42
-rw-r--r--src/dialogs/JoinRoom.cpp (renamed from src/dialogs/JoinRoom.cc)10
-rw-r--r--src/dialogs/JoinRoom.h30
-rw-r--r--src/dialogs/LeaveRoom.cpp (renamed from src/dialogs/LeaveRoom.cc)8
-rw-r--r--src/dialogs/LeaveRoom.h25
-rw-r--r--src/dialogs/Logout.cpp (renamed from src/dialogs/Logout.cc)8
-rw-r--r--src/dialogs/Logout.h42
-rw-r--r--src/dialogs/MemberList.cpp10
-rw-r--r--src/dialogs/MemberList.h61
-rw-r--r--src/dialogs/PreviewUploadOverlay.cpp (renamed from src/dialogs/PreviewUploadOverlay.cc)6
-rw-r--r--src/dialogs/PreviewUploadOverlay.h61
-rw-r--r--src/dialogs/ReCaptcha.cpp10
-rw-r--r--src/dialogs/ReCaptcha.h28
-rw-r--r--src/dialogs/ReadReceipts.cpp (renamed from src/dialogs/ReadReceipts.cc)10
-rw-r--r--src/dialogs/ReadReceipts.h58
-rw-r--r--src/dialogs/RoomSettings.cpp27
-rw-r--r--src/dialogs/RoomSettings.h126
-rw-r--r--src/emoji/Category.cpp (renamed from src/emoji/Category.cc)0
-rw-r--r--src/emoji/Category.h59
-rw-r--r--src/emoji/ItemDelegate.cpp (renamed from src/emoji/ItemDelegate.cc)0
-rw-r--r--src/emoji/ItemDelegate.h43
-rw-r--r--src/emoji/Panel.cpp (renamed from src/emoji/Panel.cc)4
-rw-r--r--src/emoji/Panel.h66
-rw-r--r--src/emoji/PickButton.cpp (renamed from src/emoji/PickButton.cc)0
-rw-r--r--src/emoji/PickButton.h53
-rw-r--r--src/emoji/Provider.cpp (renamed from src/emoji/Provider.cc)0
-rw-r--r--src/emoji/Provider.h45
-rw-r--r--src/main.cpp (renamed from src/main.cc)6
-rw-r--r--src/notifications/Manager.h55
-rw-r--r--src/timeline/TimelineItem.cpp (renamed from src/timeline/TimelineItem.cc)8
-rw-r--r--src/timeline/TimelineItem.h380
-rw-r--r--src/timeline/TimelineView.cpp (renamed from src/timeline/TimelineView.cc)8
-rw-r--r--src/timeline/TimelineView.h426
-rw-r--r--src/timeline/TimelineViewManager.cpp (renamed from src/timeline/TimelineViewManager.cc)2
-rw-r--r--src/timeline/TimelineViewManager.h94
-rw-r--r--src/timeline/widgets/AudioItem.cpp (renamed from src/timeline/widgets/AudioItem.cc)2
-rw-r--r--src/timeline/widgets/AudioItem.h107
-rw-r--r--src/timeline/widgets/FileItem.cpp (renamed from src/timeline/widgets/FileItem.cc)2
-rw-r--r--src/timeline/widgets/FileItem.h82
-rw-r--r--src/timeline/widgets/ImageItem.cpp (renamed from src/timeline/widgets/ImageItem.cc)4
-rw-r--r--src/timeline/widgets/ImageItem.h108
-rw-r--r--src/timeline/widgets/VideoItem.cpp (renamed from src/timeline/widgets/VideoItem.cc)0
-rw-r--r--src/timeline/widgets/VideoItem.h51
-rw-r--r--src/ui/Avatar.cpp (renamed from src/ui/Avatar.cc)2
-rw-r--r--src/ui/Avatar.h47
-rw-r--r--src/ui/Badge.cpp (renamed from src/ui/Badge.cc)0
-rw-r--r--src/ui/Badge.h62
-rw-r--r--src/ui/DropShadow.h111
-rw-r--r--src/ui/FlatButton.cpp (renamed from src/ui/FlatButton.cc)0
-rw-r--r--src/ui/FlatButton.h185
-rw-r--r--src/ui/FloatingButton.cpp (renamed from src/ui/FloatingButton.cc)0
-rw-r--r--src/ui/FloatingButton.h26
-rw-r--r--src/ui/InfoMessage.cpp2
-rw-r--r--src/ui/InfoMessage.h47
-rw-r--r--src/ui/Label.cpp (renamed from src/ui/Label.cc)0
-rw-r--r--src/ui/Label.h25
-rw-r--r--src/ui/LoadingIndicator.cpp (renamed from src/ui/LoadingIndicator.cc)0
-rw-r--r--src/ui/LoadingIndicator.h38
-rw-r--r--src/ui/Menu.h32
-rw-r--r--src/ui/OverlayModal.cpp (renamed from src/ui/OverlayModal.cc)0
-rw-r--r--src/ui/OverlayModal.h45
-rw-r--r--src/ui/OverlayWidget.cpp (renamed from src/ui/OverlayWidget.cc)0
-rw-r--r--src/ui/OverlayWidget.h21
-rw-r--r--src/ui/Painter.h161
-rw-r--r--src/ui/RaisedButton.cpp (renamed from src/ui/RaisedButton.cc)0
-rw-r--r--src/ui/RaisedButton.h28
-rw-r--r--src/ui/Ripple.cpp (renamed from src/ui/Ripple.cc)0
-rw-r--r--src/ui/Ripple.h145
-rw-r--r--src/ui/RippleOverlay.cpp (renamed from src/ui/RippleOverlay.cc)0
-rw-r--r--src/ui/RippleOverlay.h57
-rw-r--r--src/ui/ScrollBar.cpp (renamed from src/ui/ScrollBar.cc)0
-rw-r--r--src/ui/ScrollBar.h54
-rw-r--r--src/ui/SnackBar.cpp (renamed from src/ui/SnackBar.cc)0
-rw-r--r--src/ui/SnackBar.h79
-rw-r--r--src/ui/TextField.cpp (renamed from src/ui/TextField.cc)0
-rw-r--r--src/ui/TextField.h174
-rw-r--r--src/ui/Theme.cpp (renamed from src/ui/Theme.cc)0
-rw-r--r--src/ui/Theme.h97
-rw-r--r--src/ui/ThemeManager.cpp (renamed from src/ui/ThemeManager.cc)0
-rw-r--r--src/ui/ThemeManager.h31
-rw-r--r--src/ui/ToggleButton.cpp (renamed from src/ui/ToggleButton.cc)0
-rw-r--r--src/ui/ToggleButton.h110
142 files changed, 7096 insertions, 133 deletions
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 &center, QObject *parent = 0); + Ripple(const QPoint &center, 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_; +};